In this episode of our introductory series to Redux we will take a closer hands-on look at selectors, actions, reducers, containers and how we connect React and Redux.
This series is suitable for intermediate front end developers who are starting out with Redux. Missed the first episode?
To make this blog post comprehensive yet simple and short enough to keep you excited – we will go through a very basic example and extract the most important parts.
Let’s first look at our use case and move on to our initial setup. We will demonstrate the Redux flow by building a simple Timer app.
Use case defined
Our use case looks like this:
- Dispatch an action by clicking on a button that will increment / decrement the counter.
- Action is picked up by a reducer which will return a new state object to the store.
- Store is configured at our root level of our app and exposed to all containers that care using a provider method.
- Redux and React connect using a “container” (a stateful component) where state is derived as an object via props using the function mapStateToProps.
- React handles the new state and render a new version of the Timer app if necessary.
Our Timer application looks like this (shown in the image below).
I have added a link to a github repository and a link to a demo at the end of this post.
For brevity and the sake of keeping this blog post as short as possible. I have come up with the most minimalistic setup I could think of – omitting many of the complex parts you would want in a production build.
Basically I use a very basic react starter kit and add redux on top of that, in order to create a minimalistic proof of concept. Now we can get though the basic demo without too much complexity.
The focus of this blog post is to understand the fundamentals of Redux and help you get excited about it. Redux is a complex concept used in complex situations, yet simple at its core. The least thing we can do is keeping the configuration for our project simple.
Let’s move on and look into the application structure, so that we can get a clear picture and an holistic view of our app.
For the application folder structure I want to mimic a production environment, so that we can get a picture for when Redux is actually used in a scalable application. However we won’t build our app for production as its the scope for another blog post.
All our dev code is put in a src folder. Our React starter provides simple tooling and scripts for production by minifying, compiling into a build folder (for distribution).
Actions, Reducers and Selectors
In our actions folder we keep all our [actionTypes.js] which is simply a list of constants defining our different actions. In case of multiple reducers we combine them all in the reducers folder (index.js) using the method combineReducers. We also have a configuration file for our initial state.
Components are structured by feature and are all presentational (also called dumb / functional/ stateless).
This means they only care about how the ui will look and does not care about state and connecting with Redux.
In code we define them as simple functions taking only props as parameters.
In our example we group the Timer “feature” together with all its belongings (.css, .js, .test,js etc) in its own folder. The beauty of this is that we keep it scoped and could easily move the code to another project.
We will use index.js as a “container” component inside the Timer folder to connect to Redux. It is convenient to keep the connection to Redux together with its related content but it can also be done in one place (ie. a folder called containers).
In the configureStore set up the store. We import reducers and initial state from their respective destinations. We could also apply middleware if needed.
Middleware is a way to extend Redux with custom functionality such as i.e. handing queries, apis and asynchronous requests using thunks and sagas.
Tracing an action in Redux
Let’s look at some code stepping through the use case we described at the beginning of this blog post.
1. Dispatch an action that will increment / decrement the counter.
In our components folder we keep a folder for our Timer. Here we have a file Timer.js which holds a presentational component including the UI for this piece. On line 13 and 16 we have our onclick events that when clicked will dispatch an action via props and invoke a callback (see image below).
Notice that we wrap our onclick event in a function call so that we can pass props.counter. I did this to demonstrate passing in a value. In the case for this simple app we will have access to our counter in our parent component and that would have saved us a couple of lines.
In our parent “container” in index.js our values are emitted on lines 22 and 23.
We pass our counter value as props in our callback functions at lines 12 and 16.
Here we emit yet another callback through props (incrementCounter and decrementCounter).
Next, we will use the connect() method from react-redux to optimise our timer component for use with Redux.
We provide two methods: mapStateToProps and mapDispatchToProps (both are optional). This is how we subscribe to the store.
On the return object on line 38, we have a key incrementCounter (invoked in our component in previous example). We use timerActions.incrementCounter(val) to dispatch the action and pass it to our reducer.
On line 43 we connect our application to the store.
On line 5 we import the timerActions which is our next destination.
Inside our actions folder we have two files: timerActions and actionTypes.
An action is simply an object where we define type and value (line 5).
We keep all our action type definitions as constants inside the actionTypes file for reference (line 12). Now it’s possible to use them on multiple places such as in reducers and tests while making changes in one place
2. Action is picked up by a reducer
Inside our reducers folder we have three files: timerReducers, initialState and index.
Reducers are pure functions, think of them as meat grinders. They accept two parameters current state and action (line 6) and returns (spits out) the new state.
Note that we import our initialState, add default argument to our state variable and set our counter to the value of 0 (line 4, 6 and 25). This is a great example on ES6 features default parameters.
On line 8 we use a switch statement to handle multiple if statements. Since all reducers are called at all times regardless of which action type is passed in – it is important to always return an untouched object of the state.
If however, an action with a matching type is called (i.e. INCREMENT_COUNTER) we would return an object with a modified state (line 11).
In our index.js we combine our reducers using combineReducers provided by Redux. We pass in a name that will be used in our mapStateToProps function later on. We use the name timer instead of timerReducer (line 36) just to make it a bit more terse and readable. This is an example on the ES6 features object literal shorthand.
3. Create and expose the store to our components
In our store folder we keep our configuration file for our store.
On line 2 we import our rootReducer that we just combined.
On line 5 we return a createStore object passing in our reducers and initialState. We can pass in a third argument to apply middleware in case we are doing asynchronous requests.
Now its time to create an instance of our store and expose it to the context of our app. We do that using the react-redux library. On line 3, 4 and 5 we import timer, configuration and Redux Provider function.
On line 9 we create a reference to our store.
On line 21 we wrap our Timer app in a Provider passing in the store. React uses its built in context here to make the store available to all “containers” in our app using Connect. Remember where we started? Stay with me now, we are almost there!
4. Connecting through mapStateToProps
We are back in our index.js file (container) inside our Timers folder. This time take a closer look at mapStateToProps. mapStateToProps is our gateway that connect our store and our app.
On line 32 we receive the new state object. We return the counter object (line 33) which will be passed as props in our container. Sweet! Now all React has to do is play its magic and render a new version of the DOM for display in the ui.
Worth mentioning is that mapStateToProps is the place to make any last time adjustments to the state which is often done using selectors. More on that another day.
5. Render new version of the app
Finally to wrap this up. React notice a change in state has occurred and is smart enough to only re-render the affected parts (line 11. The counter will now display the value of 1.
Woah! That was quite a lot to cover to demonstrate the easiest possible app using Redux. Hopefully you learned something along the way and find this useful. I use surge (surge.sh) to publish the app to the web and github to host the source code (view links below).
Feel free to comment on this thread if any questions etc. 😀