Learning React can be a struggle, I can relate, and everyone who will eventually read this article can too but I aim to simplify those hard components in React. One thing that is very unique to React is dealing with states to update the User Interface and handle states the useState hook is used. The useState hook is very easy to learn it can be learned in under 2-3 minutes max but as you begin to work on more complex websites you begin to see the limitations of the useState react hook. This is just like how small spoons or cups are used for babies but when a child gets older he/she realizes how limiting those cups or spoons are and reaches for a bigger spoon. I hope this analogy hits home 😅😂😂.
UseReducer() 101: What is this hook about?
The useReducer() hook is a React hook that is used to handle complex state management. And yes, this useReducer is just like the useState for state management but with a few differences which we will talk about in this article. One important thing to note about the useReducer hook is that you can keep track of multiple pieces of state with a more complex data structure, such as an array or an object. After reading this article, you will not only understand useReducer but also know when to use it properly.
The UseReducer Syntax
const [state, dispatch] = useReducer(reducer, initialState);
This is the syntax for the useReducer hook which is quite similar to the useState syntax below;
const [value, setValue] = useState(0)
In both syntax, we get the state object and a function to manipulate that state but the useState accepts one argument and useReducer accepts two - to three arguments, this is one of the differences.
The first argument that the useReducer accepts is the reducer function. This is a function that will take in the state and action object as parameters. Depending on the action object, the reducer function must update the state in an immutable manner, and return the new state.
The second argument is the initial state. The initial state is the value the state is initialized with which is required and will return an error if not defined.
How to start using useReducer hook
To start using the useReducer hook, the first thing you have to do is import the hook from React like this:
import { useReducer } from 'react';
After importing your useReducer hook, you can go on to use it in your code. Let us look at how we can start using useReducer in our code.
Building a counter with useReducer
We will be building a counter in this article with the useReducer hook then later on, we will see how to go about it with the useState.
Step 1: Import the useReducer hook from React
import { useReducer } from 'react';
Step 2: Let us define what we will be working with.
import { useReducer } from 'react';
const initialState = {
count : 0
};
export default function Demo () {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h1>I am a counter</h2>
<button>+</buuton>
<p>{count}</p>
<button>-</buuton>
</div>
)
}
In the code block above, I have defined a lot of things and we will be working on the functionality next. The first thing we will be doing is writing the reducer function. It is in the reducer function that most of the functionality will be written. The reducer function as I said earlier takes in a copy of the state and action as an argument. Then depending on the type of the action, it is going to make some changes to that copy of the state and return the state, so let us jump back into the code.
function reducer(state, action) {
switch (action.type) {
default:
throw new Error('action unknown');
}
}
In the reducer function, we will use a switch to define new cases. The action.type contains the name of the operation to be performed. This is a string and you can set any value you want. Just make sure it's relevant to the action being performed for better readability. If the type of action in the dispatch does not correspond to any of the actions it will throw an error, so it is an important part of the code. Let us move to writing more functionality for increasing the state.
function reducer(state, action) {
switch (action.type) {
case 'increment' :
return {
...state, count: state.counter + 1;
}
case 'decrement' :
return {
...state, count: state.counter - 1;
}
default:
throw new Error('action unknown');
}
}
After defining the case, you return the state back and write the functionality you want, very easy right. Incase you are wondering how we will get the buttons working, I got you.
import { useReducer } from 'react';
const initialState = {
count : 0
};
function reducer(state, action) {
switch (action.type) {
case 'increment' :
return {
...state, count: state.counter + 1;
}
case 'decrement' :
return {
...state, count: state.counter - 1;
}
default:
throw new Error('action unknown');
}
}
export default function Demo () {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h1>I am a counter</h2>
<button onClick={() => dispatch({ type: 'increment' })} >+
</button>
<p>{count}</p>
<button onClick={() => dispatch({ type: 'decrement' })} >-
</button>
</div>
)
}
The click event handlers of the buttons use the dispatch() function to dispatch the corresponding action object and this is how to use useReducer in your code, simple, right? Let us write the same code using the useState React hook just for comparison.
Building a counter with useStateBuilding a counter with useState
import { useState } from 'react';
export default function Demo () {
const [count, setCount] = useState(0);
function increase () {
setCount(count + 1)
}
function decrease () {
setCount(count - 1)
}
return (
<div>
<h1>I am a counter</h2>
<button onClick={increase} >+</button>
<p>{count}</p>
<button onClick={decrease} >-</button>
</div>
)
}
This is how the counter looks when writing it with the useState React Hook. I know it still looks easy but imagine you have like 5 states that depend on each other, you will have to keep writing different states and functions as opposed to the useReducer hook where you can define everything in the reducer function.
How to know when to use useState and UseReducer React Hook
PreferuseStateif you have:
- JavaScript primitives(string, boolean, number) as a state( eg. our first use case )
- The state being managed is Simple business logic
- Different properties that don’t change in any correlated way and can be managed by multiple useState hooks
Prefer useReducerif you have:
- JavaScript objects or arrays as a state ( eg. our second use case )
- Different properties tied together that should be managed in one state object
Conclusion
I hope this article was quite explanatory for you and helped you understand the basics of useReducer hook. I want you to challenge yourself and try to implement the useReducer hook in our next React project rather than relying on useState it alone. I would like to hear from you in the comment section.
Top comments (9)
I used useReducer just a few time now and it always felt a little bit too overcomplex. I can't wrap my head around it somehow, but hey... it works and I know how to use it. This really helps to understand it clearly. Great explanation, thanks!
Thank you very much, Eelco! Your comment literally made my day
Very nice explanation ! I generally use reducer whenever I have a complex state that is passed through the context. In those cases, I can just have two contexts : one that contain the state and another one that contain the updater functions. This is very good for perf improvements
Thank you very much, Ndeye
Thanks, for sharing with the community
Thank you for reading Kudzai Murimi. Please don’t forget to share with your friends
Informative, Thanks for sharing!
Thank you for reading 🥺. Please don’t forget to share the article to your friends too
Thank you very much, Ajan shomodder