Go With The Flow
Introduction to data management in React applications
React is not a framework.

Freedom is not free
- how do I store and manage data?
- how do I do AJAX?
- routing?
- animations?
- tests?
- deploy?
- ...


Introduction to data management
Agenda:
- root component's state
- container components
- Flux
- Redux
Root component's state

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>
);
}
}
Example
Root component's state
Pros and cons
simple
not scaleable
Root component's state
hard to use with deep components tree
only for prototypes and very small apps
Container (Smart) Components

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'));
No logic in root anymore
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>
);
}
}
CommentBox now just a sub-app
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>
);
}
}
RandomCat is another sub-app
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>
);
}
}
There can be many of them
Container Components
Presentational (Dumb) 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>
);
}
Presentational (Dumb) Components
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>
);
}
Presentational (Dumb) Components
Container Components
Container Components

Pros and cons
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
Flux

fluxus — “flow” in Latin

One-way data flow
Flux

It's simpler than you think
Flux

Data Hub
View
“do something”
“done”
React Component
Store
Emitting event:
Dispatching action:
Flux in a nutshell
Flux
Dispatcher
+
Event emitter
+
The idea of one-way data flow
Dispatcher
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
Action creators
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
});
}
});
}
};
Store
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);
Store
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;
}
};
}
Component: closing the circle
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
Component: data from the store
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
Pros and cons
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
Redux

* unofficial logo
*
Three principles of Redux
Redux
-
Single source of truth
The whole state of your application is stored just in one place
-
State is read-only
You can not change it from outside directly
-
Changes are made with pure-functions
They called reducers and they react to the actions sent by components to change the state
Actions: creating
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));
Actions: dispatching
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 });
},
// ...
}
Reducers
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)
};
}
Store: creating
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*!'
}
]
});
Store: subscribing
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()
Data flow
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


+
Connecting to React
Redux
React: Container -> Presentational
Redux

React: passing the store
Redux
import { Provider } from 'react-redux';
const store = Redux.createStore(app, {
comments: [
// ...
]
});
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
);
React: container component
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);
React: presentational component
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
Perf optimizations
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() {
// ...
}
}
Dev-tools and time travel
Redux

Pros and cons
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
Links
General:
Presentational and Container components
Flux:
Official website
Event emitters: eventemitter3, fbemitter, emmett
Redux:
Documentation
Thanks!
Github: https://github.com/mistadikay/
Twitter: https://twitter.com/mistadikay

E-mail: iam@mistadikay.com
Go With The Flow
By Denis Koltsov
Go With The Flow
Introduction to data management in React applications
- 857