This article is about the benefits of using the Redux library in your React application, followed by a detailed description on how to use Redux. These descriptions use code snippets from a open-source sample application which contains a React application called Kanban inside a ASP.NET Core web application.
This article follows on from my initial article Templates for building React.js front-ends in ASP.NET Core and MVC5 where I describe a number of example web applications which show how to create and build React.js applications inside various ASP.NET web sites.
- Templates for building React.js front-ends in ASP.NET Core and MVC5
- This article: Using a Redux store in your React.js application
- Adding mocking to React.js Unit Tests
- Unit Testing React components that use Redux
When I created the samples I used the Kanban React app from the excellent book “Pro React” in two of the samples which used a library called Flux to simplify the handling of states. However, in my review of the Pro React book on Amazon I said that the only down side of the Pro React book was it used Flux instead of the newer Redux library to implement stores. The author saw my review and left a comment to say an alternative version of Chapter 6 using Redux was now available.
This made me think – how about I change the Kanban app in one of the samples to use Redux, using Cássio’s new chapter as a guide? Well, I have done just that and this article is all about how to use Redux in your React application.
UPDATE: It turns out Cássio has also upgraded the Kanban app to Redux – see the new branch chapter6-redux on the Pro React github site. While seeing that would have saved me doing my own refactor I would not have learnt as much as I did by doing the work myself. However it does give me another version I can look at to see if I could have done things better. I comment on this later.
The aims of this article
- To explain why using Redux is useful in a React.js application.
- Describe the architecture of a React.js application that uses Redux.
- Provide a point-by-point description of the different parts of a Redux/React application. That makes the article very long, but hopefully more useful.
- Provide links to an example Redux/React application on GitHub.
Supporting material
This article is backed up with a number of ASP.NET projects which I have made available as an open-source Visual Studio solution, AspNetReactSamples, on GitHub. This currently contains four projects, but two of particular interest:
- ReactWebPack.Core, which has a Kanban app I converted to use the use Redux store.
- ReactWebPack.MVC5, which has the original Kanban app which uses the Flux store.
While these examples are aimed at ASP.NET MVC all the React code will work in any system supporting Node.js. You can try all the React code using Visual Studio Code, which is a free tool that runs on multiple platforms.
In addition React/JavaScript samples are available from the code associated with “Pro React” book, including a version of the Kanban application that does not use a store, but the more basic hierarchical approach (see below for an explanation of the difference).
UPDATE: As I said earlier Cássio has added a chapter6-redux version. It is very interesting to compare our two implementations. I found one area where I think Cássio’s implementation is better (I point it out later), but on the whole I am happy that my solution is pretty similar, especially as Cássio is the expert in this area.
NOTE: All the code in this article and supporting material is using the React JSX coding style and the new ECMAScript 6 (ES6) JavaScript format. If you aren’t familiar with these styles (I wasn’t until a few months ago) then it will look strange to you at the start. I haven’t found any problems embracing either JSX or ES6 format, in fact I like it a lot, but if you haven’t seen it before it looks wrong.
Introduction to why Redux is useful
(Skip this section if you already know React)
I’m going to assume you at least know what React is. If you don’t then I recommend reading the article Thinking in React and maybe some of these articles [1, 2, 3]. I would also really recommend the book “Pro React”, which I think is excellent.
React is very hierarchical, with various components put together in a parent-child relationship to create the final UI component. In small applications you can pass data downwards using React this.props and return data changes back up the hierarchy via callback functions. See diagram on the right of the Kanban app using this ‘chaining’ approach.
In small React applications then using a ‘chaining’ approach is fine. However when you are building something bigger then this approach starts to cause problems. These problems have resulted in the development of what are generically known as a ‘store’ to handle the changing data. The benefits of using a store over a ‘chaining’ approach are:
- In the ‘chaining’ approach the data access and business logic is spread throughout the various React components. As you will see later a store takes data access and business logic code out of the components and into separate modules. This leaves the React parts handling just the UI and lets another part of the application handle the changing data. This makes for a much better separation of concerns and hence the application is easier to understand and refactor.
- When using a ‘chaining’ approach then each level in the hierarchy passes the callbacks down to the next lower levels. This makes refactoring React components difficult because they are inextricably linked through these chains. See this version of the Kanban application that does not use a store and note the passing of callbacks in most of the components.
In a store based implementation the ‘store’ takes over the handling of data that can change. All the react components then link to the store to a) to trigger an action on an event and b) to get updated if something changes.
There are many versions of these stores: Flux was one of the first, but the Redux library has shown to be a good alternative. In my opinion Redux is exceptionally good at separation of concerns, with a clear break between the Store that holds the data, the code that changes the store (Reducers) and the Actions that the components call to ask for a change.
The flow of control in the Kanban app using Redux is shown below, with the various steps numbered 1, 2, 3:
The text below describes each step using two words that Redux uses, Action and Reducer, which I explain in more detail later.
- Action: When an event happens inside a React component it now calls an Action, via a Redux function called dispatch. An action returns an object which always contains an action type.
- Reduce: Redux calls all the reducers which then use the action type to decide which reducer method should be run. The reducer’s job is to updates the Redux store for this action type, using any extra data that the action may have included.
- Update: Because the store has changed Redux calls a specific function in any component that registered with it. This function allows part/all of the new store values to be copied into the component’s props. Changing the props automatically causes the component to call its render function.
Details on the parts of a Redux solution
In this long section I will go detail each of the parts you need to add/change in your React application to use Redux. I will link to the actual code example application I refactored, which means you can try out the final applictaion.
1. The Redux Store
The simplest part about Redux is the store itself. Its job is to hold the data and it has a small list of API commands that make things happen. In this case we will use two commands to setup and create the store. Below is the total content of the file store/reduxStore.js which creates the single store:
import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import throttle from 'redux-throttle'; import reducers from '../reducers/index'; const defaultThrottleOption = { // https://lodash.com/docs#throttle leading: true, trailing: false } const throttleMiddleWare = throttle(500, defaultThrottleOption); const reduxStore = createStore( reducers, applyMiddleware(thunk, throttleMiddleWare) ); export default reduxStore;
The Redux store is designed that you create just ONE store, and the code above is my code to do that. Because Kanban, like most sensible apps, uses async API accesses then we need the slightly more complex setup using the Redux applyMiddleware feature. This allows us to add special handling for certain features such as async functions. The two features I add are:
- The ‘redux-thunk’ library, which provides async handling (described later)
- The ‘redux-throttle’ library, which provides a debounce or throttle feature which I need for some of the functions that get fired while a drag-and-drop move is under way.
2. Actions
Actions are what your various React components call, via Redux’s dispatch function, to get anything done. Some of your actions may request data, e.g. fetchCards(), and some may do some more business-like logic, e.g. updateCardStatus(cardId, listId). The point is that all data access or business logic is placed in the action rather than in the React component where the event happened. That way the React components can focus on the display of the data and the actions can focus on the logic to change the data.
Note: The removal of this.state from a React component allows you to use a simpler form of React component called a “stateless functions“. Well worth looking at for simple react components, like buttons, or setting styles etc. This article has some more information on stateless functional components.
In any reasonable sized applications you will have multiple action files. In Kanban there are two: actions/CardActionCreators.js and actions/TaskActionCreators.js. In the Kanban app’s case the split is fairly arbitrary, and I think the CardActionCreators.js file could be split further.
Now down to the detail. In all cases an action will, eventually, return an object with at least a type variable, which contains a unique type. The simplest form is just that – a function returning a JavaScript object. Here is an example:
toggleCardDetails(cardId) { return { type: constants.TOGGLE_CARD_DETAILS, payload: {cardId} }; }
Note: As you can see in the example above, the type is taken from the object constants. The norm is to create file which holds all the action types and always refer to type via this. This makes sure you don’t make a mistake when typing an action type in different places in the application.
The second form is a function that returns another function of a specific signature. To use this form you need the store to include redux-thunk middleware you saw in section 1. This function form can be used in two cases:
a. Handling actions that use async functions
An async function is a function that kicks off a task and returns immediately before the task has finished, e.g. a request for data from an external source. Once the task finishes it will return a new result that Redux needs to pick up.
To handle this async case your initial action needs to return a function, not an object. Here is an example which uses Kanban’s dispatchAsync function to call dispatch with the correct success/failure when the async task ends:
fetchCards() { return (dispatch) => { dispatchAsync(KanbanAPI.fetchCards(), dispatch, { request: constants.FETCH_CARDS, success: constants.FETCH_CARDS_SUCCESS, failure: constants.FETCH_CARDS_ERROR }); } },
b. When the returned object needs to be calculated
If you need to execute some JavaScript code before you return a result then you need to use the function return format. The action below needs to extract the card id and status before it can persist the card’s change in status. Notice the second parameter, getState, which gives you access to the store’s content.
//Warning: This type of usage does not support server-side rendering. //see this SO answer for more on this http://stackoverflow.com/a/35674575/1434764 persistCardDrag(cardProps) { return (dispatch, getState) => { let card = getCard(getState().cards, cardProps.id) let cardIndex = getCardIndex(getState().cards, cardProps.id) dispatchAsync(KanbanAPI.persistCardDrag(card.id, card.status, cardIndex), dispatch, { request: constants.PERSIST_CARD_DRAG, success: constants.PERSIST_CARD_DRAG_SUCCESS, failure: constants.PERSIST_CARD_DRAG_ERROR }, {cardProps}); } },
NOTE: the comment at the top of this code that points out that this technique for accessing the store does not work when doing server-side rendering. Because this application is not using server-side rendering this approach is OK here. However please read this SO answer for more on this subject.
3. Reducers
Redux defines a very specific approach to how the store is changed. It does it through what it calls ‘reducers’. A reducer is a function that take the current state of the store and a specific action object and produce a new, immutable (unique) store content. I think of reducers as using a repository pattern, i.e. some code that turns a client centric command, e.g. updateCardStatus(cardId, listId), into some sort of change to the data in the store.
The reducers are only called by the Redux store, i.e. you don’t call them yourself. It is the action type provided by the Action that defines which reducer method is called to alter the store. There are often multiple reducers, with different reducers looking after different bits of the stored data. In my case I have a reducers/cardReducer.js and a reducers/draftCardReducer.js. Redux calls all the reducers and lets them decide what should be run.
The normal pattern for a reducer is to use switch statement using the type variable in the action object (see code below) to select the right method to run. While you could use other approaches using a switch does make it obvious what is happening when looking at the code. Here is the start of my cardReducer.js:
import constants from '../constants'; import { getCardIndex } from '../cardUtils' import update from 'react-addons-update'; const initialState = []; const cards = (state = initialState, action) => { switch (action.type) { case constants.FETCH_CARDS_SUCCESS: return action.payload.response; /* * Card Creation */ case constants.CREATE_CARD: return update(state, {$push: [action.payload.card] }) case constants.CREATE_CARD_SUCCESS: cardIndex = getCardIndex(state, action.payload.card.id); return update(state, { [cardIndex]: { id: { $set: action.payload.response.id } } }); //... lots more case statements default: return state; } } export default cards;
A few things to note:
- The function is called at the start with no parameters. This results in the state being set to the initialState, in this case an empty array, and because the action will be undefined the default path will return that state.
- In each place where we change the store we use the update command from the ‘react-addons-update’ library. This does an immutable update, i.e. it creates a brand new object while leaving the old state as it was. That is very important to Redux, but I don’t have the space to go into why – read this for some reasons why Redux does this.
- The name of the function, in this case cards, is important as it the name that is given to the part of the store that this reducer looks after. That means that the command getState().cards will return the data that this cardReducer deals with.
4. Making the store available
Because Redux has only one store we need to make it available to any component that needs to call an Action or be updated if the store changes. The first step is to get the store known to React at the top level. For that we use the ‘react-redux’ class Provider which apply as an outer class on our app’s main entry point. Here is link to my App.js which also uses a router etc, but below is simple example make it more obvious what is going on:
let store = createStore(myReducers) render( <Provider store={store}> <YourApp /> </Provider>, document.getElementById('root') )
5. Accessing the store in React components
5.a The standard approach
Having used the Provider class at the top level we can access the store using Redux’s connect function. I will give an example of how I set up the use of Redux in my KanbanBoardContainer.js file with some code snippets. The most-used approach is to supply two functions to the connect function:
- mapStoreToProps: this is called when the store changes and copies the part of the store that it needs into a local prop, or multiple props. In this case we read all the cards into the prop.cards.
- mapDispatchToProps: this allows you to set up functions that will call the actions you need, but linking them into Redux via Redux’s dispatch In this case the class only needs the fetchCards function, and that gets mapped to this.props.fetchCards inside your React component.
Note: Redux calls the first function mapStateToProps, rather than my mapStoreToProps. I changed the name as ‘state’ has a specific meaning in React and the name ’mapStateToProps’ really confused me at the start, as I thought it was referring to React’s this.state… .
Below is the end of the components/NewCard.js file from the Kabban app. You can see the two functions and how they are used to a) subscribe to Redux’s update if the store changes and b) make some actions available inside the component.
//... the start of the class has been left out function mapStoreToProps(storeState) { return { draft: storeState.draftCard } } function mapDispatchToProps(dispatch) { return { updateDraft: (field, value) => dispatch(CardActionCreators.updateDraft(field, value)), addCard: (card) => dispatch(CardActionCreators.addCard(card)), createDraft: (card) => dispatch(CardActionCreators.createDraft(card)), } } export default connect(mapStoreToProps, mapDispatchToProps)(NewCard)
Once you have done this an action can be called via the props, e.g. this.props.addCard(this.props.draft). Also, if the Redux store is changed then mapStoreToProps is called and, in this case, updates the props.draft variable, which in turn causes React to run the render method inside this component.
Tip: Quicker linking to actions
A variation of the mapDispatchToProps is to use Redux’s bindActionCreators function. This is a quick shorthand that binds all the actions in an action creator to this.props.actions.<youraction>. You can see an example in the components/CheckList.js.
I used the bindActionCreators function in CheckList because the react component called all of the actions defined in TaskActionCreator, so it was easier to use the bindActionCreators function. I tend to hand code when I use a subgroup of the actions so that I’m not linking to an action I don’t use. Maybe I’m being too picky – just using Redux’s bindActionCreators function is certainly easier. You can decide what you want to do.
5.b The minimal approach to calling an action
Sometimes you only want to access say one action, and going through all the mapping seems overkill. In this case you can use the react-redux’s connect function, but without any parameters. In this case the Redux function dispatch is added as this.props.dispatch and you can call a action using it, e.g.
this.props.dispatch( CardActionCreators.updateCardStatus(dragged.id, props.id))
I do this in the components/List.js component.
5.b The minimal approach to accessing the store
It is possible (but highly unlikely – it is not good practice) that you want to access the store directly. You can use something called the React context which, if you click the React context link, you will see it has a ‘use with care’ notice on it, so be warned.
However the react-redux Provider class we used to make the store available throughout the application places link to the store in this.context.store. You can access it in any React component by adding the following code to YourClass (very similar to propTypes, but for React context)
<YourClass>.contextTypes = { store: React.PropTypes.object }
I thought I needed to do this in component/EditCard.js and implemented the use of context. Now I know more I might have used the normal mapStoreToProps, but I left the EditCard code as it always good to have an example of using the context to refer back to.
Update: Cássio’s EditCard.s implementation is, in my opinion, much better. He uses mapStoreToProps approach with access to ownprops. Here is his code in the EditCard.js file:
const mapStateToProps = (state, ownProps) => ( { draft: state.cardDraft, card: getCard(state, ownProps.params.card_id) } );
Unit Testing
As I said at the start I think Redux does a great job of separating each of the parts of the problem into separate areas. In addition the Actions and Reducers are completely self-contained and know nothing about the Redux store. As the Actions and Reducers contain some of the key code then this makes Unit Testing much easier.
Rather than make this article even longer I refer you to Redux’s useful Writing Tests section.
UPDATE: I have started to extend the Unit Tests in the sample application, especially around testing the Redux version. You can read about how I mock out modules in the new article Adding mocking to React.js Unit Tests and see my current React Unit Tests in the ReactWebPack.Core project under the JsUnitTests directory.
Conclusion
Firstly, well done for getting this far, as it is a long article.
Like every new technology I approach Redux was a struggle to understand at the start, but once I began writing some code, finding it didn’t work and fixing it then it began to come together. GitHub tells me it was only four days, but it seemed longer as there were a number of new approaches I hadn’t seen before. Hopefully this article, which was partly written as I was doing the upgrade, will help you on your own journey with Redux.
From my experience of using Redux I think it is a very nice tool. I especially like its very clear separation of each different aspect: store, actions and reducers. That means I only have to think about one problem space when I am working in each part – which frees me to be much more focused and productive.
I hope this has been helpful. If you spot anything I misunderstood (I am still learning!) then do let me know.
UPDATE: I have written another new article called Adding mocking to React.js Unit Tests which adds a new capability to my Unit Testing tools. This is especially relevant to the Redux version of the Kanban application as some of the tests I have created are aimed at testing the action parts of the Redux system.
Happy coding!