Introduction to data management in React applications
Agenda:
image is borrowed from: http://andrewhfarmer.com/react-ajax-best-practices/
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
comments: []
};
}
componentDidMount() {
DB.get({ success: this.updateData });
}
updateData = comments => {
this.setState({ comments });
};
handleCommentSubmit = comment => {
DB.put({ data: comment, success: this.updateData });
};
render() {
return (
<div className="commentBox">
<h1>{'Comments'}</h1>
<CommentList comments={this.state.comments} />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
}
Root component's state
simple
not scaleable
Root component's state
hard to use with deep components tree
only for prototypes and very small apps
image is borrowed from: http://andrewhfarmer.com/react-ajax-best-practices/
// Root component can be used just for composing container components
function App() {
return (
<div className="app">
<Header>
<RandomCat />
</Header>
<CommentBox />
<ContactInfo />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('app'));
Container Components
Container Components
class CommentBox extends React.Component {
constructor(props) {
super(props);
this.state = {
comments: []
};
}
componentDidMount() {
DB.get({ success: this.updateData });
}
updateData = comments => {
this.setState({ comments });
};
handleCommentSubmit = comment => {
DB.put({ data: comment, success: this.updateData });
};
render() {
return (
<div className="commentBox">
<h1>{'Comments'}</h1>
<CommentList comments={this.state.comments} />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
}
Container Components
// each Container Component is responsible for its own data
class RandomCat extends React.Component {
constructor(props) {
super(props);
this.state = {
cat: ''
};
}
componentDidMount() {
DB.getCat({ success: this.updateData });
}
updateData = cat => {
this.setState({ cat });
};
render() {
return (
<div className="random-cat">
<img src={this.state.cat} />
</div>
);
}
}
Container Components
class ContactInfo extends React.Component {
constructor(props) {
super(props);
this.state = {
contacts: []
};
}
componentDidMount() {
DB.getContacts({ success: this.updateData });
}
updateData = contacts => {
this.setState({ contacts });
};
render() {
return (
<div className="contact-info">
<ContactList contacts={this.state.contacts} />
</div>
);
}
}
Container Components
function Header({ children }) {
return <div className="header">{children}</div>;
}
function Contact({ contact }) {
return (
<div className="contact">
<h3 className="contact-type">{contact.type}</h3>
<i className="contact-value">{contact.value}</i>
</div>
);
}
function Comment({ author, children }) {
return (
<div className="comment">
<h2 className="commentAuthor">{author}</h2>
{children}
</div>
);
}
Container Components
function CommentList({ comments }) {
return (
<div className="commentList">
{comments.map(comment => {
return (
<Comment author={comment.author} key={comment.id}>
{comment.text}
</Comment>
);
})}
</div>
);
}
function ContactList({ contacts }) {
return (
<div className="contact-list">
{contacts.map(contact => <Contact key={contact.type} contact={contact} />)}
</div>
);
}
Container Components
Container Components
still simple
more or less scaleable
Container Components
ok to use with deep component tree
problems when components need the same data in different places
data flow is hard to manage and reason about
hard to debug
fluxus — “flow” in Latin
Flux
Flux
Data Hub
View
“do something”
“done”
React Component
Store
Emitting event:
Dispatching action:
Flux
Dispatcher
+
Event emitter
+
The idea of one-way data flow
Flux
// create dispatcher instance
const AppDispatcher = new Flux.Dispatcher();
class CommentBox extends React.Component {
// ...
handleCommentSubmit = comment => {
comment.id = Date.now();
// create an action
AppDispatcher.dispatch({
actionType: 'NEW_COMMENT',
comment
});
// ...
}
// ...
}
action
Flux
class CommentBox extends React.Component {
// ...
handleCommentSubmit = comment => {
ActionCreators.addComment(comment);
}
// ...
}
const ActionCreators = {
addComment(comment) {
comment.id = Date.now();
AppDispatcher.dispatch({
actionType: 'NEW_COMMENT',
comment
});
DB.putComment({
data: comment,
success(comments) {
AppDispatcher.dispatch({
actionType: 'COMMENTS_UPDATED',
comments
});
}
});
}
};
Flux
class CommentStore {
onDispatch = payload => {
// ...
};
}
const commentStore = new CommentStore();
AppDispatcher.register(commentStore.onDispatch);
class CommentStore extends EventEmitter {
onDispatch = payload => {
switch (payload.actionType) {
case 'NEW_COMMENT':
this.emit('NEW_COMMENT', payload.comment);
break;
}
};
}
const commentStore = new CommentStore();
AppDispatcher.register(commentStore.onDispatch);
Flux
class CommentStore extends EventEmitter {
constructor() {
super();
this.comments = [];
}
getComments() {
return this.comments;
}
onDispatch = payload => {
switch (payload.actionType) {
case 'NEW_COMMENT':
this.emit('NEW_COMMENT', payload.comment);
break;
// update data in the store and notify components
case 'COMMENTS_UPDATED':
this.comments = payload.comments;
this.emit('COMMENTS_UPDATED');
break;
}
};
}
Flux
class CommentBox extends React.Component {
// ...
componentDidMount() {
commentStore.on('NEW_COMMENT', this.addComment);
// ...
}
addComment = comment => {
this.setState({
comments: this.state.comments.concat([ comment ])
});
};
handleCommentSubmit = comment => {
ActionCreators.addComment(comment);
};
// ...
}
1. create an action
2. update the state only when that action made a full circle
Flux
class CommentBox extends React.Component {
constructor(props) {
super(props);
this.state = {
comments: commentStore.getComments()
};
}
componentDidMount() {
// ...
commentStore.on('COMMENTS_UPDATED', this.updateData);
ActionCreators.getComments();
}
updateData = () => {
this.setState({
comments: commentStore.getComments()
});
};
// ...
}
1. create an action
2. update the state
good for middle-size and big applications
easy for components to get the data they need
might be hard to understand
unclear how to store data
no common patterns -> no ecosystem
Flux
data-flow is predictable
server-rendering is hard
hard to debug
* unofficial logo
*
Redux
Redux
// turn data into action
const ActionCreators = {
addComment(comment) {
return {
type: 'NEW_COMMENT',
comment
};
}
};
// dispatching is somewhere outside of action creators
store.dispatch(ActionCreators.addComment(comment));
Redux
// function dispatching created action
function addComment(comment) {
return store.dispatch(ActionCreators.addComment(comment));
}
// we are passing this function to the component
function CommentBox() {
return (
<div className="commentBox">
<h1>{'Comments'}</h1>
<CommentList comments={this.props.comments} />
<CommentForm onCommentSubmit={addComment} />
</div>
);
}
// component calling this function
class CommentForm extends React.Component {
handleSubmit = e => {
// ...
this.props.onCommentSubmit({ author, text });
},
// ...
}
Redux
// transforming the state depending on dispatched action, data, etc.
function commentsReducer(state = [], action) {
switch (action.type) {
case 'NEW_COMMENT':
return state.concat(action.comment);
case 'COMMENTS_UPDATED':
return action.comments;
default:
return state;
}
}
// each reducer is responsible only for its piece of data
function rootReducer(state = {}, action) {
return {
comments: commentsReducer(state.comments, action)
};
}
Redux
// store is a singletone with two arguments: root reducer and initial data
const store = Redux.createStore(rootReducer, {
comments: [
{
author: 'Pete Hunt',
id: 1388534400000,
text: 'Hey there!'
},
{
author: 'Paul O’Shannessy',
id: 1420070400000,
text: 'React is *great*!'
}
]
});
Redux
// every time the state changes
let unsubscribe = store.subscribe(function() {
console.log(store.getState())
)
// dispatch some actions
store.dispatch(ActionCreators.addComment({ author: 'mom', text: 'hi' }));
// > Object { comments: Array[3] }
store.dispatch(ActionCreators.addComment({ author: 'dad', text: 'bye' }));
// > Object { comments: Array[4] }
// Stop listening to state updates
unsubscribe()
Redux
React
Component
Action
Creator
// dispatching action
{
type: 'NEW_COMMENT',
comment
}
comment
Root
Reducer
Store
// current state
{
comments: Array[3]
}
Comment
Reducer
// action
{
type: 'NEW_COMMENT',
comment
}
// part of the state
// (comments)
Array[3]
// changed part
// of the state
Array[4]
// changed state
{
comments: Array[4]
}
// to subscribers
{
comments: Array[4]
}
1
2
2
3
4
5
6
Redux
Redux
Redux
import { Provider } from 'react-redux';
const store = Redux.createStore(app, {
comments: [
// ...
]
});
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
);
Redux
import { connect } from 'react-redux';
// combine state selectors and map them to props
const mapStateToProps = state => {
return {
comments: state.comments
};
};
// create functions for dispatching actions and map them to props
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onCommentSubmit(comment) {
dispatch(ActionCreators.addComment(comment));
}
};
};
// generate Container component
const AppCommentBox = connect(
mapStateToProps,
mapDispatchToProps
)(CommentBox);
Redux
// doesn't know about Redux
class CommentBox extends React.Component {
render() {
return (
<div className="commentBox">
<h1>{'Comments'}</h1>
<CommentList comments={this.props.comments} />
<CommentForm onCommentSubmit={this.props.onCommentSubmit} />
</div>
);
}
}
// part of the state is coming through props
// function for dispatching an action
Redux
class SomeComponent extends React.Component {
shouldComponentUpdate(nextProps) {
// props will never change by reference
// because redux store is immutable
if (nextProps.data === this.props.data) {
return false;
}
return true;
}
render() {
// ...
}
}
Redux
good for middle-size and big applications
easy for components to get the data they need
might be hard to understand
clear patterns on how data should be stored
HUGE ecosystem (middleware, debug tools, etc.)
Redux
data-flow is predictable
server-rendering is easy
very easy to debug
General:
Presentational and Container components
Flux:
Official website
Event emitters: eventemitter3, fbemitter, emmett
Redux:
Documentation
Github: https://github.com/mistadikay/
Twitter: https://twitter.com/mistadikay
E-mail: iam@mistadikay.com