DEV Community

Cover image for useReducer instead of useState while calling APIs!
Yogini Bende
Yogini Bende

Posted on • Updated on

useReducer instead of useState while calling APIs!

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
diogo405 profile image
Diogo Goncalves

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:

import useSWR from 'swr'

function Profile() {
  const { data, error } = useSWR('/api/user', fetcher)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>
  return <div>hello {data.name}!</div>
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
stereoplegic profile image
Mike Bybee • Edited

Or React Query. But yes, separate server fetching from local state.

Collapse
 
flexbox profile image
David Leuliette 🤖 • Edited

@stereoplegic
Do you know what are the differences between swr and react-query?

I use both and I have no idea how to pitch the differences 😆

Thread Thread
 
stereoplegic profile image
Mike Bybee

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

Collapse
 
rexgalilae profile image
RexGalilae

This! No need to reinvent the wheel every time you want an API dependent state variable

Collapse
 
hey_yogini profile image
Yogini Bende

I have used react query and it is quite good. I didn't know about swr though. Will check this out. Thanks for sharing

Collapse
 
binajmen profile image
Benjamin

I fully agree. Don't reinvent the wheel. There's also URQL if you're dealing with GraphQL
formidable.com/open-source/urql/do...

Collapse
 
jharteaga profile image
Jose Arteaga

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!!

Collapse
 
bpitts8019 profile image
Bradley Pitts

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.

Collapse
 
jharteaga profile image
Jose Arteaga

Very clear your explanation Bradley, got it! Thanks so much!

Collapse
 
antontsvil profile image
Anton T

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!

const [state, dispatch] = useReducer(userDetailsReducer, initialState);
const [likeState, likesDispatch] = useReducer(userLikesReducer, initialLikesState);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jharteaga profile image
Jose Arteaga

Thanks @antontsvil ! That helped me as well to understand that it is possible! great example!

Collapse
 
esigaraantalya7 profile image
Info Comment hidden by post author - thread only accessible via permalink
Elektronik Sigara Antalya

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.

Collapse
 
hey_yogini profile image
Yogini Bende

Wrong article to post your advertisement sir!

Collapse
 
ravirajtambile profile image
Info Comment hidden by post author - thread only accessible via permalink
Ravirajtambile
Collapse
 
ravirajtambile profile image
Info Comment hidden by post author - thread only accessible via permalink
Ravirajtambile
Collapse
 
yukiyohure profile image
Yuki Shibata

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??

Collapse
 
hey_yogini profile image
Yogini Bende

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 😇

Collapse
 
yukiyohure profile image
Yuki Shibata

That makes sense!
Thank you for answering🙌

Collapse
 
abiolaajibola profile image
Abiola

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.

Collapse
 
rmaurodev profile image
Ricardo

Great post! Thanks for sharing.

Collapse
 
lizardkinglk profile image
sndp

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.

Collapse
 
hashimlokasher profile image
Hashim Lokasher

Great Post

Collapse
 
z4ck987 profile image
Z4ck987

I think Yogesh Chavan must use this when he create another tutorial next time.. 😀

Collapse
 
spyda247 profile image
Babajide Ibiayo

Excellent Article...thanks for sharing.

I noticed you dispatched a action of type ACTIONS.SUCCESS in the event of an Error response status. Is this a typo?

Collapse
 
hey_yogini profile image
Yogini Bende

Good catch! It was a type.. fixed it 😇
Thank you 🙌

Collapse
 
peterwd1 profile image
Peter

What do you think about calling API directly inside of reducer functions?

Collapse
 
rubenruvalcabac profile image
Ruben Ruvalcaba

Great tip. I've faced many times updating more than one state and never felt well updating each one separately. This technique is lengthy but cleaner and prioritize separatation of concerns.
Thanks!

Some comments have been hidden by the post's author - find out more