In the last few years, React has continuously gained popularity for the development of web applications. At Keyhole, we have several blogs talking about React and related technologies, including React, Formik, react-router, and many others.
So why would we need Redux? Quite often when we develop applications, we start with small pieces. As the business requirements change, new features/modules/components are added/removed/updated. Particularly in enterprise applications, you may end up with a deep hierarchy of parent-child relationships.
In a React application, parent component-states are passed down to its child component as property. Application states can be changed in many different places. If not managed perfectly (and, in many cases, it’s not), your system can behave differently than expected. It can become increasingly difficult for development, debugging, production support and code maintenance.
This is where Redux comes into play. Using Redux with React solves this common problem with the concepts of central data store & application state.
In this blog, I’ll talk about Redux and explain how it can benefit React front-end development. I’ll provide an introduction to using Redux with React and show a demonstration of reconstructing an example React application to React + Redux.
Our Example
The application that our example will begin with is the Keyhole “Now Playing” React application. The repository is located here: https://github.com/in-the-keyhole/khs-react-course.
I’ll re-construct this React application into two projects. The first project will be the back-end server application which will handle all the typical business in the server end, like registration, authentication, database operation, etc. I’ll use MongoDB to persistent data and Node.js for REST API development. You can also reference RESTful API development to the Github repository open source khs-convo, released by Keyhole Software.
The second project will be pure front-end development, which will React with Redux for state management. React with Redux integration is the focus of this blog.
If you are eager to see the final project, git clone these two repos: https://github.com/in-the-keyhole/khs-movies-server.git and https://github.com/in-the-keyhole/khs-movies-client.git. Construct a local build by using the following instructions provided in README; you can take a look of the running application in your browser at http://locahost:3000.
Introduction to Redux
So what is Redux and how does it relate to React?
Redux is a predictable state container for JavaScript applications. In its simplest form, it is a library to manage the state of any JavaScript application.
The following figure demonstrates the React + Redux flow.
The right side of the figure as shows the key element for Redux:
- Store: holds all application store into a single object tree.
- Reducers: only way to change state by emitting an action.
- Actions: specify anything that could change the application state.
The left side shows the integration of Redux with React:
- Provider: makes the application state contained in store available to container.
- Containers Component: responsible for an external service call to retrieve application data, container also maps the state and dispatches action to component.
- Presentational Components: the presentation layer.
This is the overall flow in a React + Redux application. Now let’s go through each individual piece with examples.
On the Redux Side
Store
To have Redux manage an application’s state, all state must be contained within a single object. At the minimum, to create a store, you need to have root reducer and initial state.
'react-router-redux' and initial state object. import {createStore} from 'redux' import { routerReducer } from 'react-router-redux' const initialState = { movies: [] }; const store = createStore(rootReducer, initialState ) export default store;
With this configuration, the state movies
can be managed by Redux store. As you can see, the initial state is an empty array; we will need to create Reducer
to change the movies’ state.
Reducers
Reducers are responsible for changing application state with the emitting of actions.
As you can imagine, the functions for a Movies Reducer are to retrieve movies and perform searches on the movies you retrieved. Reducers are a pure function. In our example, it returns the action payload based on its type. I’ll show you later on how to create action.
import {c} from '../constants' const movies = (state = { }, action) => { switch (action.type) { case c.LOAD_MOVIES: return action.payload; case c.SEARCH_MOVIES: return action.payload; default: return state } } export default movies
Actions
Anything that can cause application state change must happen in Actions.
We have previously identified two type of actions that changes state of movies: LOAD_MOVIES
and SEARCH_MOVIES
. This is the Load
movies action implementation; a similar implementation for search action could be found on git repo here.
import {fetChMovies, changeRating} from '../services/movie.js'; import {c} from '../constants'; export function preloadMovies() { return (dispatch) => { loadMovies()(dispatch); }; } function loadMovies(dispatch) { return (dispatch) => { fetChMovies().then((movies) => { allMovies = movies; dispatch(loadTheMovies(movies)); }); }; } function loadTheMovies(movies) { return ({type: c.LOAD_MOVIES, payload: movies}); } export const movieActions = { preloadMovies } export default movieActions;
In this example, I have, added a service layer of fetchMovies
by calling RESTFul API that is published by khs-server application. You can take a look at the implementation details here. By calling dispatch(loadTheMovies(movies)
, the movie Reducer will update the movies state.
On the React Side
So by this point, we have demonstrated that application state can be managed by Redux. We have not, however, yet integrated state change with React. React and Redux can be integrated together with the help of provider
offered by the react-redux
package.
Provider
The Provider glues the Redux store together with the React component. This means that the application state information in the single store is available for React components.
For a React application, you must define the render of the root application element like this:
const element = <App /> ReactDOM.render(element, document.getElementById('root'))
In order to have Redux work with React, you will need to do this:
const element = <App /> ReactDOM.render( <Provider store={store}> <ConnectedRouter history={history}> <div className ="container" id="wrapper"> <div> <App/> </div> </ConnectedRouter> </Provider>, document.getElementById('root'))
As you can see, the React application is now managed by the provider which is associated to the Redux store. ConnectedRouter
from react-router-redux
is also used to provide application routing.
Through Provider, the React component is able to refer to the state information contained in Redux store. Based on the different roles in integrating with Redux, React component can be separated into two categories: presentational and container components.
Presentational Components
Container Components
Presentational Component: MovieList
Let’s demonstrate this. The presentation component MovieList
is responsible for displaying the movie’s information.
import React from 'react' import Movie from './Movie.js' const MovieList = ({ Movies }) => { return ( <div className="movie-container"> <br/> <div> <ul> {movies.map(movie => <li key={movie.id}> <Movie title={movie.title} poster={"/" + movie.poster_path} id={movie.id}/> </li>)} </ul> </div> </div> ) } export default MovieList;
Container Component: MoviesContainer
The container component called MoviesContainer
is used to wrap up the presentation component movies.
import {connect} from 'react-redux' import {Component} from 'react' import {bindActionCreators} from 'redux' import React from 'react' import {withRouter} from 'react-router-dom' import MovieList from '../ui/MovieList.js' import MovieActions from '../../actions/movie.js' class MoviesContainer extends Component { componentWillMount() { this.props.preloadMovies(); } render() { return <MovieList movies={this.props.movies/> } } const mapStateToProps = (state, props) => ({movies: state.movies }) const mapDispatchToProps = dispatch => bindActionCreators(MovieActions,dispatch); export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MoviesContainer))
The container component MoviesContainer
is responsible for data fetching by dispatching reducer action as followed: this.props.preloadMovies
. You may be wondering why preloadMovies is a property here and not an action, and how it could work with Redux?
Connect
from the react-redux
package does the trick. Connect is used to map the store’s state and dispatch to the property of a component. As you can see from this example, the state movies
is passed as a property to the presentation component.
Conclusion
What I discussed above are the main components for pre-loading movies. The git repository provides more features, like user registration, login, logout, the ability to search for movies by name, rating movies, to list a few examples. Please check out those features for further reference.
You will quickly realize that the implementation pattern is consistent when a new feature is added and the responsibilities for each component are clearly defined. Most importantly, application state change is unidirectional, which means its more predictable, easier to manage, and the code is more maintainable.
Redux is designed to manage state for single-page applications. We have discussed how the unification of React + Redux provides a powerful tool for web application development. We have increasingly seen more Keyhole clients choosing the React route for web development front-end technology. I personally recommend they also integrate Redux to manage state in the earliest stage of development.