Hello folks!
It’s been a while since React has introduced Hooks and we all fell in love with it’s patterns and ease of use. Though this is the case, many of us do not leverage all the features, hooks provide and useReducer
is one of them! Because useState is the hook which we learn first, we do not make much use of useReducer
hook. So in this article, I will be focussing on useReducer
and will walk you through the best use-cases to implement it.
So, let’s dive in!
What is useReducer?
useReducer is another hook used for the modern state management in React. This concept was introduced in Redux first and then it is adapted by React as well. Typically, reducer is a function which accepts two arguments - state and action. Based on the action provided, reducer will perform some operations on a state and returns a new updated state. In context of React, useReducer
also performs similar state management. You can read more about useReducer in detail in the react documentation
How to use it for API calls?
You must have got the basic idea of useReducer hook till now. Let’s just dive straight into the code and understand how using useReducer will make our code more efficient over useState.
Let’s first start with an API call using simple useState. It will look something like this -
// user component using useState
const User = () => {
const [userDetails, setUserdetails] = useState();
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
useEffect(() => {
setLoading(true);
const getUsers = async () => {
let response = await axios.get('/users');
if (response.status == 200) {
setUserdetails(response.data);
setError(false);
return;
}
setError(response.error);
};
getUsers();
setLoading(false);
});
return (
<div>
{loading ? (
<p>loading...</p>
) : error ? (
<p>{error}</p>
) : (
<ul>
{userDetails.map((user) => (
<li key={user.id}>
<h1>{user.name}</h1>
<p>{user.location}</p>
</li>
))}
</ul>
)}
</div>
);
};
export default User;
This is a very basic API call. In real life scenarios, we have to manage more states than this. But for starters, let’s assume we have 3 states to manage and those are dependent on each other. When our application gets more complex, at times, we end up defining more than 7-8 states. In such scenarios, if we are using only useState, then it becomes very tedious to keep track of all the states and to update them synchronously.
To solve all these problems, a better approach is using useReducer. Let’s see the same API call using useReducer.
// user component using useReducer
const ACTIONS = {
CALL_API: 'call-api',
SUCCESS: 'success',
ERROR: 'error',
};
const userDetailsReducer = (state, action) => {
switch (action.type) {
case ACTIONS.CALL_API: {
return {
...state,
loading: true,
};
}
case ACTIONS.SUCCESS: {
return {
...state,
loading: false,
userDetails: action.data,
};
}
case ACTIONS.ERROR: {
return {
...state,
loading: false,
error: action.error,
};
}
}
};
const initialState = {
userDetails: '',
loading: false,
error: null,
};
const User = () => {
const [state, dispatch] = useReducer(userDetailsReducer, initialState);
const { userDetails, loading, error } = state;
useEffect(() => {
dispatch({ type: ACTIONS.CALL_API });
const getUsers = async () => {
let response = await axios.get('/users');
if (response.status == 200) {
dispatch({ type: ACTIONS.SUCCESS, data: response.data });
return;
}
dispatch({ type: ACTIONS.ERROR, error: response.error });
};
getUsers();
});
return (
<div>
{loading ? (
<p>loading...</p>
) : error ? (
<p>{error}</p>
) : (
<ul>
{userDetails.map((user) => (
<li key={user.id}>
<h1>{user.name}</h1>
<p>{user.location}</p>
</li>
))}
</ul>
)}
</div>
);
};
export default User;
Here, we are using a dispatch function to call our reducer. Inside the reducer, the switch case is defined to handle the actions provided by the dispatch function. The actions object declared above will make sure that every time we pass predefined action to the dispatch function. You can skip that step and use strings directly. Inside each switch case, we are performing operations on the state given and returning a new state.
I know your first reaction seeing the code would be, this looks lengthy! But trust me, it makes more sense. The useReducer hook accepts two parameters, a reducer function and initial state. Reducer function will perform all the state updations on the state provided. But what are the benefits of doing this?
-
State will update in a single function, based on the action and it will be dependent on previous.
When we pass action to the reducer, we tell it what operation to perform on a previous state. This way, we can make sure all the states are in sync with that operation and there is a very less chance of missing any updates on a state.
-
Easy to manage complex states
As one function is updating states, it is easier to manage complex states containing arrays and objects. We can useReducer effectively to handle updates on objects and arrays.
-
Easy to test and predictable
Reducers are pure functions and perform operations based on predefined actions. Hence, they do not have any side effects and will return the same values when given the same arguments. This makes them predictable and easy to test when implemented.
When to choose useReducer over useState?
useReducers are good to choose over useState but not every time. If your use case is simple, they will add unnecessary complexity to your code. I use this couple of rules to choose useReducer over useState -
1. If there are many states dependent on each other.
2. If the state is a complex object.
I hope these rules will help you as well to decide which state management hook to go for. If you have any other factor to choose between these two, let me know in the comments.
Thank you for reading this article! Hope it will help you in some way. You can also connect with me on Twitter or buy me a coffee if you like my articles.
Keep learning 🙌
Top comments (28)
I'd say use swr (swr.vercel.app/) when calling APIs, it returns the data, error, and if it's loading (plus cache, deduplication, etc). Here's a snippet from their website:
Or React Query. But yes, separate server fetching from local state.
@stereoplegic
Do you know what are the differences between
swr
andreact-query
?I use both and I have no idea how to pitch the differences 😆
Tanner Linsley (author of React Query) details the differences here (also comparing Apollo and RTK Query) much better than I can: react-query.tanstack.com/comparison
This! No need to reinvent the wheel every time you want an API dependent state variable
I have used react query and it is quite good. I didn't know about swr though. Will check this out. Thanks for sharing
I fully agree. Don't reinvent the wheel. There's also URQL if you're dealing with GraphQL
formidable.com/open-source/urql/do...
Great post! it helped a lot to understand much better useReducer. Now, I'll try to put it into practice to remember its usage. Just quick question. Can I have more than one useReducer inside the Function Component? Because in the example there is just one and you call the dispatch method, however if you have more than one useReducer, how I can make the difference which I am working with? Thanks!!
While it is possible to use more than one useReducer for the state of a component. I would recommend against it as each use of the useReducer hook will return it's own state object and dispatcher. Instead design the single reducer to control the whole state of the component. Any defined action in the reducer should return the whole state of the component with any expected updates as per that defined action.
Very clear your explanation Bradley, got it! Thanks so much!
To help understand hooks, you might wanna look up array destructuring (similar to object destructuring). In short - you can use whatever names you like when destructuring an array, so just use different names for your second useReducer hook!
Thanks @antontsvil ! That helped me as well to understand that it is possible! great example!
Antalya elektronik sigara bayisi olarak son güncel teknolojiyi takip ediyor ve sağlam işe yarayan ürünleri sizlere sunuyoruz. Ekibimiz bu konuda bir hayli mesai harcamakta ve pod mod Antalya Antalya firması olarak olabildiğince hızlı kargo çıkarmaktayız. En iyi pod mod elektronik sigara modelleriniz Antalya merkez dahil tüm ilçelere dağıtım yapmaktayız.
Wrong article to post your advertisement sir!
Nice🖤
Two line shayari in Marathi
Beautiful
Motivational Quotes in Marathi
Best 💟
50+ WhatsApp Status in Hindi
Nice🖤
Two line shayari in Marathi
Thank you for the awesome post!
This makes my understanding of the useReducer deepen.
I've wondered that Is that a usual way to define an API-call function IN the useEffect hooks??
If you don't need to use it outside the hook, there is no harm in declaring it inside. The only purpose of declaring a function there is to enable
async-await
. But again.. its our choice 😇That makes sense!
Thank you for answering🙌
Nice and informative post!
I would also want to avoid using useReducer to manage local state if I am using Redux for 'global' state management, it obviously can get confusing.
Great post! Thanks for sharing.
Great post. Simple and Nice. Btw, I always check if async await call returned data and ready to render because sometimes it says UserDetails.map is not a function. This way very helpful.
Great Post
Some comments have been hidden by the post's author - find out more