Redux hooks code-along: to-do app
A how-to guide for adding this state management library to an existing React app
Yesterday, I made a simple to-do app using React and a Rails backend, which has some functionality of adding and deleting tasks. Though this is small application without large amounts of application state that are needed in many places (in other words, not the best candidate for using a state management library), I thought it would be an easy and clear example of how to add Redux hooks to manage state.
- Note, this tutorial will not go over the backend setup, but you can take a look at that code here
My Current Code
To briefly run through the current structure and behavior of my main component, it is fetching my database at the ‘days’ endpoint (which is in the form of a JSON file) with the hook
useState to set the result of the fetch to the state (all the days from the database in an array datatype), and rendering the Day component for each day in the state (only dealing with one day for this example).
I do something similar in the Day component — to only see what’s relevant to state management, take a look at these three snippets:
I have fetched all the tasks from my database (with
useEffect again) and set them to a state of
allTasksso that they are all rendered at once, and updated without a page re-load if a task gets deleted. I am passing this state of
allTasks to each Task component, and if I want to delete a task, that function needs to be passed down to be triggered when the button for each Task is clicked.
Let’s start adding Redux into the mix…
Starting with Redux — Explained in words
First, let’s install the redux dependencies (specific for React in my case):
npm install react-redux//or if you are using yarnyarn add react-redux
Let’s define the functions in this app that when called (by some user action or a result of a component mounting) involve changing some data or state —
- fetching the days (GET request)
- fetching the tasks (GET request)
- adding a task (POST request)
- deleting a task (DELETE request)
These will guide our actions, which are:
“plain objects describing what happened in the app, and serve as the sole way to describe an intention to mutate the data.”
Actions are not directly responsible for changing the state, however. This is managed by the store, which controls the dispatch, reducer, and current state.
Following the above diagram, our actions are dispatched, and the store updates the state by passing the current state and action to the reducer, and the state gets mutated to what the reducer returned.
Getting into the Code
Let’s create a new folder from the root directory called actions, and a new file called
actionConstants.jswhere we define our action types — types should be defined as string constants for consistency’s sake and avoiding errors anywhere in the app.
Let’s make the action for fetching days, in a file called
getDays is returning a function because we are making use of thunk. More on that later.
We need to import the action types first (I decided to use a success and failure type, but that’s not necessary). Inside the function, let’s make the fetch request return a dispatch of type
FETCH_DAYS_SUCCESS and set the payload (that will be carried along) as the object result of the request, so that when this action is called, it will return this dispatch.
Reducers are functions that accept state and an action in the argument, which help calculate the new state value, but they cannot alter the existing state. Instead, they must make copies (i.e. with the spread operator, etc.) and make changes to those copies.
Define the state as initialState, and for each action type, have a different return value for the state depending on the action. For getting the days from the database, take a copy of the state and set the days object to the result of the fetch request in the
action.payload), which is an array of day objects.
Thinking of the other reducers we will need, make a rootReducer file, which combines the reducers, ultimately turning different reducing functions into a single reducing function that we can pass to
createStore (which builds the store, holding the complete state tree of the app.)
*TL;DR: reducers are functions that return the next state tree, given the current state tree and an action to handle.
Now, before we add this to our Main component, we need to wrap our application in a
<Provider >… </Provider> component to render the store available throughout the component tree (so that we can dispatch actions and change state anywhere).
createStore accepts a reducing function and (optional) an enhancer with third-party capabilities such as middleware (the only store enhancer that ships with Redux is
applyMiddleware(). Backing up to that previous mention of thunk in our getDays action creator —
Middleware allows you to write action creators that return a function instead of an action and thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met.
Free code camp provides a great explanation for why it’s useful for this type of application:
One of the main use cases for this middleware is for handling actions that might not be synchronous, for example, using axios to send a GET request. Redux Thunk allows us to dispatch those actions asynchronously and resolve each promise that gets returned.
Now, moving back to what we need to change in the Main.js file. Instead of using the
useState hook to set our state, we are going to make use of the
The selector will be called with the entire Redux store state as its only argument, and within that state, we can access the data we need (here, need the days array from the
FETCH_DAYS_SUCCESS action). It will run whenever the component renders. Let’s set it to a days constant so that we can still use the map function to return a Day component for each day.
useDispatchhook returns a reference to the
dispatch function from the Redux store, and should be used to dispatch actions. We want to dispatch our action to fetch the days in
useEffect and ultimately have the reducer update the state to that data. Now that we’ve implemented redux for the Main component, the same functionality should be working now as at the beginning of this tutorial.
If you’re familiar with Redux , you’ll notice we haven’t used the higher order function
mapDispatchToProps. These hooks give access to the Redux store and dispatch actions without having to wrap your components in
connect(), allowing for less and more semantic code in my opinion.
OTHER ACTIONS & REDUCERS
Virtually the same logic is employed for fetching and displaying the tasks in the Day component, so I will not rehash that process, and adding / deleting tasks is only slightly different.
Starting with the
addTask action creator, the only main difference from the
getDays creator is the necessary arguments required for a successful POST request (since we need to define the attributes necessary to create a new task).
Additionally, we set the
payload to the new task created.
Let’s take a look at the
tasksReducer next —
tasksReducer contains a switch statement for each action that can be dispatched. Here, for the add task action, we are pushing the new task (
action.payload) into the task array, and returning that updated state. You can apply similar logic for deleting a task (action creator not shown here) for returning a state with the deleted task removed.
These two snippets (above and below) of the Day component show how we are using
useDispatch to access the store, make use of the state, and dispatch actions. The
add function dispatches
addTask, accepting the arguments needed to pass to the action creator for the POST request.
Here, the component is mapping over the allTasks array (which we got from the
useSelector hook) to render a Task component for each object in that array.
If you made it this far, hopefully you found this helpful as an introduction to Redux hooks (or Redux overall) as a state management option available for your next React app. To see the entirety of my code, you can take a look at my github repo here.