A Long Time Coming
I feel like this blog has been in the making for a few weeks now. Redux as a state management tool makes so much sense as apps get larger and just seemed like the logical next step in my React tool belt. Redux at a high-level is very easy to understand and implementing it is a lot easier than I thought. After making a few components and connecting them to your store it really becomes muscle memory. However using it is going to make your state management life improve ten fold.
Define
In the word of the Redux team it’s “A Predictable State Container for JS Apps”. Essentially you remove the storage of state from your React components and keep everything in one central store
that exists outside of the component hierarchy. You connect components to pieces of state they need through accessing this store
and passing the state as props to the component. You can also make changes to your store from inside the component when you need to make a change. We do this using dispatch
and a reducer
.
Installation
React-redux is just like any other package and is installed using your package manager of choice. You can add it to your project with npm install react-redux
or yarn add react-redux
. After installed you will need to do several things before you can start thinking about getting state in your components from the Redux store. You’ll need to do the same with the redux package npm install redux
.
Wrap the App with Provider and Create the Store
At the top level of your application you need to wrap your App component with the Redux Provider
. This will give your entire app access to the store ( don’t worry we’re about to talk about that ). The provider is imported at the top of the index.js
file. It’s here in index.js
that we will create our store. Redux does this through the createStore
function. This is imported in the same way as the Provider
was from react-redux
. We’re going to pass the store we create to the Provider
as a prop The createStore
function takes at least one argument, a reducer. Which is where your initial state object, and list of dispatch actions is going to live. See below for the sample index.js
file. This snippet assumes you’ve made a reducer.js
file on the same level as index.js
. We will take care of that next.
Create a Reducer File
The reducer is where a lot of the action is going to happen when it comes to making changes to your store. In its very basic form a reducer could just return your initialState. Once we have the store connected to some components we are going to start changing that pretty fast. A simple reducer file is going to be made up of only a couple of things.
Initial State=> this is where we set the starting values for the pieces of state we are going to manage. This can be as simple as an object with one key value pair or can be as nested and complicated as your heart’s desire.
The Reducer Function=> it’s here where our store is going to be changed based on information we pass to this reducer. We could change a count
value and increment it by one, for example. We will make a case for many different actions as we start to grow our application. Here’s a sample reducer file that simply returns the initialState in all cases. Not very useful. Let’s work on that next.
As you can see the reducer function takes in two arguments, the prevState
or what we have in state before running the reducer, and the action
. We will get into actions more in a little bit, for now know actions are going to be called in components, sent to this reducer and something is going to happen to state based on a case in the reducer function and what we want to happen in our application.
Hooking up a component
Components are connected to our store via the connect
function imported from react-redux
. The connect function provides the connected component with pieces of data that it needs from the store
, and the functions it can use to dispatch actions
to the store. I think about four steps when connecting a component.
1 . import { connect } from 'react-redux';
2. Add the connect function next to the export of the component. For Example if our component was named Counter it would look like this.
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
3. define the mapStateToProps
function we passed to connect
=> mapStateToProps
is where we can reach out to our state and grab the pieces that we need to use in this particular component. To stick with the counter idea we would need to know what the current count is. When we set up our state count was the key so this is how we will access it.
const mapStateToProps = state => {
return {
counter: state.counter
};
}
We have successfully taken a piece of state from our store and made it available to our component via props. <div> Current Counter: {props.counter} </div>
will display Current Counter: 0 when we boot our app up for the first time.
4 define the mapDispatchToProps
function we passed to connect
=> mapDispatchToProps
is how we are going to send a message to our store and tell it that we need to update something inside of it. We do this by returning an object of keys where the values are dispatch
functions that take an object as an argument and must contain a key of type with a value. Here’s a quick example we can talk about.
const mapDispatchToProps = dispatch => {
return {
incrementCounter: () => dispatch({type: 'INCREMENT'})
}
}
We’ve now created a dispatch function. We can call this function in our component, say in an onClick
listener and it will go to the reducer and search for what it is supposed to do. The above example is simple, but we can make the object much more complex if we wanted. Let’s say we want to have a button in our counter to increment the count by 100 instead of 1. We can add another dispatch in mapDispatchToProps and give it a payload to bring with it.
const mapDispatchToProps = dispatch => {
return {
incrementCounter: () => dispatch({type: 'INCREMENT'}),
incrementByOneHundred: () => dispatch({type: 'ADD_HUNDRED, payload:{value: 100})
}
}
Here we’ve given our incrementByOneHundred
key a function where we not only pass the type
, but a payload
which is an object that has a key of value pointing to a number (100). We can now add both of these functions to onClick listeners. They won’t work yet though. We first have to handle their cases in the reducer.
Handling a Dispatch in the Reducer
Now that we have some click listeners hooked up we are going to hit that reducer function and need to have a way to handle these cases. Here’s our reducer function from before, but we’ve added to our switch statement to handle the additional dispatches we just wrote.
We’ve added two cases to our switch statement to accommodate the new actions we built in our Counter component. When these cases hit our switch they are going to be handled accordingly. It is important that the case matches the type EXACTLY or it isn’t going to work. You’ll also notice that we’re accessing that value we sent along as well through action.payload.value
Once the component is hooked up and you are handling the cases in the reducer. You should be seeing changes to your state as you’re making them in your project.
I shied away from Redux for a long time simply because I heard other people had trouble with it, or people said it was ‘hard’. Don’t do what I did. Try to build a simple one page app with a counter where you can increase, decrease, add forty, divide by 7 whatever you want. Get it working on one simple component like this first and then take on the challenge of converting one of your old projects to use Redux. Your brain will thank you for it. If you have trouble? Reach out to me on LinkedIn, I’d be happy to help and talk through some of the Redux basics.