In previous lessons, you've already learned about the most used React hooks like useState
or useEffect
. And in this lesson, we will learn about a React hook called useReducer
. So let's get started.
React provides a number of powerful features that allow developers to create highly interactive and responsive web applications. One of these features is the useReducer
hook, which is a powerful tool for managing state in React.
The useReducer
hook is:
- an alternative to the
useState
hook (to some extent). - they are both hooks that are meant to produce state.
- and whenever the state changes, the component is going to automatically re-render.
The big difference between useState
and useReducer
is in the steps involved in implementing state management using both of these hooks. The useReducer
hook is most useful when you have multiple pieces of state that are very closely related to each other.
Now, let me explain what do I mean by that. Say, in our Smarter Tasks application, we have to implement a component to show the list of all projects, right? Now that component can be implemented using both useState
and useReducer
hook. So, the plan is: first, I'll create the ProjectList
component using useState
, and then I'll recreate the same component using the useReducer
hook. After that, I'll explain the differences.
To start with, we will create a new component called ProjectList.tsx
in the src/pages/projects
folder.
And then we'll do the first implementation using the useState
hook.
import React, { useState, useEffect } from "react";
import { API_ENDPOINT } from "../../config/constants";
interface Project {
id: number;
name: string;
}
const ProjectList: React.FC = () => {
const [projects, setProjects] = useState<Project[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => {
// Fetch the list of projects here
fetchProjects();
}, []);
const fetchProjects = async () => {
const token = localStorage.getItem("authToken") ?? "";
try {
setIsLoading(true);
const response = await fetch(`${API_ENDPOINT}/projects`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
setProjects(data);
setIsLoading(false);
} catch (error) {
console.log("Error fetching projects:", error);
setIsLoading(false);
}
};
return (
<div>
<h2>Project List</h2>
{isLoading ? (
<div>Loading...</div>
) : (
<ul>
{projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)}
</div>
);
};
export default ProjectList;
Here,
- we have two component states,
projects
andisLoading
. - The
projects
state handles the array of projects which we are fetching from the API. - The
isLoading
state is used to show aLoading...
text when the API call is in progress. - In the
fetchProjects
function, we are using the two setter functionssetIsLoading
andsetProjects
to update local component state. - And finally, we are showing the list of projects.
Now we can import this component in src/pages/projects/index.tsx
file to load the list of projects there:
import ProjectList from "./ProjectList";
const Projects = () => {
return (
<>
<h2>Projects</h2>
<ProjectList />
</>
);
};
export default Projects;
Now let's go to the browser to see if the list of projects is coming or not.
Open http://localhost:5173/account/projects in browser to see the projects, also open the browser console.
So, as you can see, in the network console, the API call for /projects
path is working and we are getting the successful response. Now, as our current database is empty, that's why the project list is not coming.
To fix it, we can add one or two projects from the Postman REST client.
Action: Add 2 projects for current organisation Now let's go back to the browser to see if this is working.
Action: Re-Open http://localhost:5173/account/projects in browser And yes! This time the project names are showing up. Great!
Now, this implementation looks quite straight-forward, isn't it?
Now, the same component can be implemented using the useReducer
hook. Let's do that
- First, we'll remove the implementation of
useState
. To start with, I'll remove the import statement ofuseState
hook, then:
// src/pages/projects/ProjectList.tsx
import React, { useEffect } from "react";
interface Project {
id: number;
name: string;
}
I will start with commenting out the useState
hooks
const ProjectList = () => {
// const [projects, setProjects] = useState<Project[]>([]);
// const [isLoading, setIsLoading] = useState<boolean>(true);
// ...
// ...
Now, I will comment out the places wherever I've used setIsLoading
and setProjects
setter methods. The code will look something like this:
const fetchProjects = async () => {
const token = localStorage.getItem("authToken") ?? "";
try {
// setIsLoading(true);
const response = await fetch(`${API_ENDPOINT}/projects`, {
method: 'GET',
headers: { 'Content-Type': 'application/json', "Authorization": `Bearer ${token}` },
});
const data = await response.json();
// setProjects(data);
// setIsLoading(false);
} catch (error) {
console.log('Error fetching projects:', error);
// setIsLoading(false);
}
};
// ...
// ...
}
- Next, we will import the
useReducer
hook from the 'react' library:
import React, { useEffect, useReducer } from "react";
// ...
// ...
Now, inside the component, I'll write a const, and inside a square bracket I'll write state, dispatch. Then we will use the useReducer
hook, where the first argument is going to be something called reducer and the second argument will be an object with properties like projects
and isLoading
.
const ProjectList = () => {
const [state, dispatch] = useReducer(reducer, {
projects: [],
isLoading: false,
});
};
First, let us see the left-hand side of the diagram. On the top of this diagram, we have the code that we had just a moment ago around useState
, and at the bottom is the new code that we just put in for useReducer
. So, immediately, we're going to see that there are some common elements between these two different hooks.
Whenever we call useState
or useReducer
, we get back an array with two elements inside of it. So with the first useState
hook, element one is projects
, and element two is setProjects
. Then with the second useState
, we've the first element isLoading
, and setIsLoading
as the second element. And down in the useReducer
element one is state
element two is dispatch
.
In both cases, the first variable that we get back inside that array or that first element is our state variable. That's our data. That's the thing that is going to somehow change over time. And the second element in that array is a function that we're going to use to change our state.
So up here we have the setProjects
and setIsLoading
, and down here with in useReducer
, we are always going to use the same name for this function. We're always going to call it dispatch.
Dispatch works a little bit differently than our setter functions. This dispatch
function is going to be a major focus in how we figure out how to use the useReducer
hook.
Now, let's look at the right hand of the diagram. In useState
, whenever we call it, we can put in some initial value for that piece of state.
We can do the same thing with useReducer
by providing the initial value as a second argument to the use reducer
hook. So in our case, our state is going to start off as an object with an empty projects list and isLoading set to false.
Now, these are similarities between the two hooks. But, next we will focus on the differences.
- Whenever we make use of the
useState
hook, we're going to calluseState
multiple times for each individual piece of state we want to declare. As you've already seen, we have one state variable calledprojects
, and another isisLoading
. And we usually try to keep these pieces of state as simple as possible. So, they will be hopefully a number, a string, some very simple value.
But when we make use of useReducer
, we do the opposite. We try to create just one single state variable, and we call it state. That's what we did just a moment ago. We created a piece of state, we call it very simply state. And we make that state variable an object, so that object can have many different properties inside of it, like:
{
projects: [],
isLoading: true
}
Here we are taking all the state properties that are required to make our component work correctly, and we're combining it all together into one single object. This means that when we call the useReducer
right now, we get back that state variable that is an object. And it's going to have properties like projects
, which is going to be an empty array because that's what our initial value is right now by default. And it's going to have a isLoading
set to false
.
So now inside our component, if we ever want to get access to the projects, we can refer to state.projects
.
So back in our component, we can update the JSX accordingly
return (
<div>
<h2>Project List</h2>
{state.isLoading ? (
<div>Loading...</div>
) : (
<ul>
{state.projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)}
</div>
);
Alright, that's quite a lot for a single lesson. But we've learned the similarities and differences between useReducer
and useState
hook. Let me summarize the key points once again:
- Both
useState
anduseReducer
are hooks used for managing state in React components. - The
useState
hook is simpler and commonly used for managing individual pieces of state, while theuseReducer
hook is more suitable for managing complex state that are closely related to each other. - When using
useState
, you call it multiple times to declare individual state variables, and each variable has its own setter function. - When using
useReducer
, you typically create a single state variable and use the dispatch function to update the state. - With
useState
, the state is typically a simple value like a string or a number, while withuseReducer
, the state can be an object with multiple properties. - Both hooks automatically trigger a re-render when the state changes.
- And finally, the
useReducer
hook provides a more centralized and organized approach to managing complex state in React components compared to the simpleruseState
hook.
So, that's it for this lesson. Next, we've to complete the implementation of the reducer
function. See you there.