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

original Main component (pre-Redux)
original Main component (pre-Redux)
Main.js

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 useEffect, utilizing 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:

original Day component (pre-Redux)
original Day component (pre-Redux)
Day.js
original Day component (pre-Redux)
original Day component (pre-Redux)
Day.js
original Day component (pre-Redux)
original Day component (pre-Redux)
Day.js

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
Redux lifecycle

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

ACTIONS

So let’s start putting this into code, starting with our actions. To reiterate, actions are a Javascript object that have a type attribute, which describes what happened. For example, instead of using the fetch directly in a useEffect hook, we will place that in a universal action, which will eventually get dispatched in the hook.

file that exports action strings as constants
file that exports action strings as constants
actionConstants.js

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.js:

code for my getDays action creator
code for my getDays action creator
getDays.js

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

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.

code for daysReducer file
code for daysReducer file
daysReducer.js

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 getDaysaction (action.payload), which is an array of day objects.

file that combines reducer, which will get exported and used in createStore method.
file that combines reducer, which will get exported and used in createStore method.
rootReducer.js

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.

STORE

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).

Index.js file
Index.js file
Index.js

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.

REDUX HOOKS

Main.js with redux
Main.js with redux
Main.js

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 useSelector and useDispatchRedux hooks.

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.

The 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.

Why Hooks?

If you’re familiar with Redux , you’ll notice we haven’t used the higher order function connect(), mapStateToProps, or 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.

action creator for adding tasks
addTask.js

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.js
tasksReducer.js
tasksReducer.js

The 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.

Day component with redux
Day component with redux
Day.js

These two snippets (above and below) of the Day component show how we are using useSelector and 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.

Day component passing props to child component
Day component passing props to child component
Day.js

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.

full-stack developer exploring how software can solve real-world problems | https://sarabastian.com/