In this tutorial, you will learn how to set up Redux in a simple, plain English guide. Redux is a useful package that will manage state within your ReactJS application. Redux has one downside, the number of steps it takes to set it up! I have been using Redux for several years and I still struggle to set-up a new store occasionally. The set-up process is not intuitive. Each time I need to create a Redux store, I always need to remind myself what I need to do. I spend a good hour cursing the world! I must be a dumbass as I always need to look at previous projects to remind myself what I need to set up! To save me from encountering this pain again I am creating this dummies guide to Redux.
Redux Overview
Add Redux into your project via NPM, the install command can be found here. When you use Redux to manage state, you will typically create a folder in your application called 'state'. Within the 'state' folder, you will create four sub-folders actions
, constants
, reducers
, and selectors
:
- Action: Describes the CRUD operations for your store.
- Store: The data store, the single source of truth!
- Reducers: Functions that mutate the store
- Selectors: Get and filter data return from the reducer!
In order to create a Redux store we need to follow these steps:
- Define the store
- Define actions
- Define reducers
- Define selectors
- Initialise the store
- Connect components to the store as need
Redux Store
Redux works using a store. The store is the central area where data will be saved. By connecting a component up to the store, an application-wide state management solution can be achieved. Examples of stateful things that you might want to manage include, logged-in status, items in cart, items added to wishlist, credit applied to cart etc... The code to create a store should be inside the store
folder. I tend to create a single file called configureStore.js
that looks like this:
Two things you may notice with the code above. First, we define this thunk
thing. Second, we pass a custom reducer into the store using combineReducers
. Let us examine these things.
Redux is configured using reducers. Reducers may have a fancy name, they are not complicated. A reducer is basically a function with a switch statement inside it that handles requests. You will not call the reducer directly, the store will. This is why we pass all the reducers into the store. The store is responsible for calling the reducer, whenever dispatch()
is used. Within an application, you may have multiple types of data that you want to manage. Like any well-written code, each reducer should only do one thing. Each reducer should map to one single data slice, e.g. one to deal with user status, one for items in cart etc... Instead of having to pass all the reducers into the store, we can use combineReducers
to bundle them up! and make cleaner code!
The second discussion point about the code listed above that sets up the store was the usage of thunk
. By default Redux expects actions to return data as strings. If you want to work with APIs you will want to use promises, or for the unlucky callbacks. When working with either approach you will likely want to return functions rather than strings from the code that you will shortly create in the actions
folder. This is what thunk
does. thunk
allows you to return and work with functions in actions. As you can see in the code below we return the promise. Also note this code would live in a file within the 'actions' folder!
The code to initialise the store within your application is usually added within app.js
and looks like this (notice how it connects with the main container):
Redux Events, Actions & Selectors
As mentioned we use dispatch()
to broadcast events into redux. These events will then cause the store to call the reducers. Within the reducers, we write code to look out for certain events and on match, we write some code that tells Redux what to do. On a successful call, data will need to be passed out of the store and into the calling component. Selectors can be used to filter the data in the store into a format that your component expects.
When a Redux event is triggered using dispatch()
, Redux needs a way to map an event. This is when the reducers will be called. The purpose of all the reducers is to try and map the event. This event is retrieved in the reducer using the type
argument. If a successful match is found, the reducer can manipulate the data and tell Redux what to do next. An example of how a reduced is structured is shown below (this code would like in a file inside the reducers
folder):
In Redux we also have actions. Actions are where the business logic lives, the CRUD operations. We add, delete and edit store data here. The action
folder is where you add the code to call that API, the action file is where you will request for things to be removed from the store. The files inside of actions
will be called directly from within your components (by importing them and calling them directly). The action is the initial catalyst from component to store.
In the code above a dispatch of 'FETCHSONGSSUCCESS' is made. This will trigger the store, which will then call each reducer. If a reducer has a way to handle that type, its type will be executed. Below shows what a return object might look like for a successful event. In this example, data has been returned from an AJAX request successfully:
Finally, we have selectors to filter the data into a nice format, meaning we do not need to expose more data than we want to within our components. The code to create a basic selector could look like this:
See how the selector converts the data shape into a format that the calling component cares about. Redux forces you to use good separation of concerns. Actions drive the data to be held in the store. Reducers control what happens when events are fired from within actions. Selectors manipulate the data. Selectors are usually called after a successful broadcast is made. You now have a store that manages and saves data for you. The final part of the puzzle is connecting a component to the store. Connecting is simple and is done like this:
Connecting the store works in the same manner as higher-order components. You pass your component into the store.