Redux is a tool for managing state in JavaScript applications. Prior to writing this post, I had assumed it was only used with React, but apparently it can be used with Angular and Vue.js as well. It just happens to be most frequently used with React.
Before Redux, what is known as prop drilling was used to manage state in React applications. This is where data is passed from the parent component to its child component and that component passes that data to its child component and so on. This resulted in tightly coupled code that was hard to change or refactor. Many people adopted Redux to solve this problem.
As applications grow in size and become more complex, it become increasingly important to be able to handle application state in a manageable way. Redux has the benefit of allowing developers to write applications that behave consistently and that are much easier to test and debug.
It’s important to note that in React 16.3.0, the Context API was introduced which allowed state management in React without Redux. However, Redux is still better and more performant for complex applications.
In this blog post, I am going to walk through how we can incorporate Redux into our React applications.
Without Redux
I created a simple todo app that uses prop drilling to manage state instead of using Redux. I think it makes sense to show the wrong way of handling state first because then the benefit Redux provides will be more apparent. If at any time you want to see the complete source code, you can find it here.
Initial Setup
Here’s what the user interface looks like:
And here’s what our App.js file looks like:
Our App.js is composed of two components (besides the ones from bootstrap), TodoForm and TodoList:
And in our TodoList.js, we have a TodoItem component:
That’s pretty much all of the main code. The Add Todo and Delete buttons have not been set up. The todos are being populated in App.js from an array in another file. I am going to keep Add Todo as just a decorative button, but we will add an event handler for the Delete button together.
Adding the Event Handler
However, when we go to add the event handler, we run into an issue. Our todos exist in App.js, not in the TodoItem component where the Delete button is located. We need a way to propagate upwards towards App.js where our todos reside. To solve this, we will need to create an event handler in App.js and pass it down to our TodoItem component. Let’s create a method called deleteTodo() above our return statement:
For those that don’t know, the array filter() method in JavaScript can be used to return a copy of an existing array such that all items meet meet the given condition. In this case, we use it to remove the item corresponding to the id that is passed into deleteTodo().
Next, let’s pass this method to our TodoList so we can ultimately call it in TodoItem:
Then, we need to change TodoList so that it accepts handleDelete() as a prop, and we then need to pass it once again to TodoItem:
Inside TodoItem, we need to accept handleDelete() as a prop again. Additionally, we need to pass an inline function to our onClick handler that calls our handleDelete() function and passes in the id:
Writing an inline function inside of onClick is a trick used in React to pass in parameters to our event handlers.
With those changes in place, we can now remove items from the todo list by clicking the Delete button. What we did here is known as prop drilling in React. This is not a good solution though because the code becomes less reusable and tightly coupled to the component hierarchy. Additionally, the code base becomes harder to maintain and debug, especially with larger projects.
Fortunately, Redux has a solution to this problem. Let’s look at how we can approach the same scenario with Redux.
With Redux
While Redux provides a solution for managing state in React applications, it is likely overkill in small to medium sized applications. The example above could be solved much less tediously with Context API. However, I am set on covering Redux exclusively in this post. I will probably cover Context API in a later post.
The standard way of using Redux is with the Redux Toolkit, so that’s what we’ll use here. The toolkit basically allows us to implement Redux more simply and with less boilerplate code. If you’re anything like me, you may have doubts about that statement once we get into it, which is why I opened the section with the above paragraph.
Installation
First, we want to install the Redux and the toolkit through the command line:
Creating the Store
Next, we need to add the Redux store. The store is a centralized location that holds all of the states of an application; it is the source of truth for the entire application.
Create a new folder called app in the src folder, and within that folder add a new file store.js:
Providing the Store
Now we need to bring that into index.js and wrap our App component in a Redux Provider component. We also need to pass in the store we created as a prop:
The store is now available to all components within the Provider component, which is the entire application in this case.
Creating a Slice
Next, we need to create what is known as a slice in Redux. A slice is code that relates to a specific piece of data and the actions that can manipulate its state. Create a new file src/features/todos/todoSlice.js:
Our todo slice is composed of a string identifier, an initial state, and reducer methods to define how the state can be manipulated.
Connect Reducer to the Store
The next thing we need to do is bring our todo slice into our store:
This lets the store know that we want all updates to todos to be handled by our slice reducer method.
Implementing Redux in the App
We can start seeing this in action by making some changes to App.js so that we are pulling our todos from our state and not from TodoData.js like we were earlier. Also, we can remove our deleteTodo() method as well as the handleDelete() prop we were passing to TodoList:
We used the useSelector() hook to access our todos. For those that don’t know React hooks, the docs define them as:
React’s “hooks” APIs give function components the ability to use local component state, execute side effects, and more.
Similarly, we can change our TodoItem to use the useDispatch() hook and to use our remove() reducer method so we can manipulate the state directly:
With that change, we should now be able to delete todos just as before, but without having to pass props down through multiple components.
Conclusion
Hopefully this post has given you some insight into Redux and the benefit it provides. This wasn’t the best example, but you can imagine in a large scale app how tedious it could be to have to pass props down in multiple places and through multiple layers just to change some state in the application.
At the same time, developers tend to to overuse Redux when in a lot of cases React’s Context API would be a better solution. From my research, Redux tends to be better suited for large applications while Context API is better for smaller apps.
If you have any questions or comments, feel free to reach me via the contact page. Otherwise, stay tuned for my next post!