The beginner's guide to the React useReducer Hook
Tags: javascript, react
Published: 2021-06-28 11:39:18

![The beginner's guide to the React useReducer Hook](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/02pe4nks5x27x7nf4usy.png)

Delivered in React 16.8 `useReducer` hook allows you to manage complex state logic in React Applications. The `useReducer` hook is an alternative for the `useState` hook and, combined with other interesting React’s feature called `Context`, helps manage state efficiently.

Usually, **Redux** or **MobX** are the best options for managing state in large React applications, but sometimes using 3rd party libraries is unnecessary and can kill your project. 

If you have a small project, consider using native React hooks instead of injecting extensive 3rd party libraries that add a lot of code and force you to use their architecture and approach in your project.

On the other hand, using **GraphQL** with the **Apollo** client, you use Apollo’s state management mechanisms, and using another library for state management is redundant.
 

## Understand useReducer

```javascript
const [ state, dispatch ] = useReducer(reducerFucntion, initialState);
```
The `useReducer hook` receives two params:
* **Reducer function** -  a pure function that return state depends on dispatched action 
* **Initial state** - initial state (how can I explain that?) ;-) 

The `useReducer` returns array that contains:

* **Current state** - initially, this will be the initial state that you passed to a reducer, and after each action dispatch, the state is changed and returned here.

* **Dispatch function** - a function that receives action as an argument and dispatches this action in a reducer.

**Note:** state in reducers is immutable. It cannot be changed outside the reducer, and also keep in mind that you cannot mutate state in reducer when the action “working.” 


When you want to change the state, you need to copy the current one, then mutate the copy, and at the end, return that replicated state as the current state.

### Reducer function
Take a look at the sample reducer function: 

```javascript
const ADD_MESSAGE = ‘ADD_MESSAGE’;
const REMOVE_MESSAGE = ‘REMOVE MESSAGE’;

export function MessagesReducer(state, action) {
    switch(action.type) {
        case ADD_MESSAGE: {
            return {
                messages: [
                    ...state.messages,
                    action.message
                ]
            };
        }
        case REMOVE_MESSAGE: {
            const indexToToRemove = state.messages.indexOf(action.message);

            if (indexToToRemove >= 0) {
                return {
                    messages: [
                        ...state.messages.splice(indexToToRemove, indexToToRemove)
                    ]
                }
            } else {
                return state;
            }
        }

        default: {
            return state;
        }
    }
}
```

The reducer function receives two parameters: `state` and `action`. The state is data that we are manipulating, and action determines what we want to do with that data.

Action determines what we want to do with data, and usually, an action is an abject with one obligatory field: “type.” 

In the Reducer above, we can see two actions that we handle:
* `ADD_MESSAGE`
* `REMOVE_MESSAGE`

When we want to dispatch those actions, we have to pass these objects to the reducer function:

```javascript
{type: ADD_MESSAGE, message}; // adding message
{type: REMOVE_MESSAGE, message}; // removing message
```

As you can see, I passed the second parameter there: “message,” and I have access to it in the reducer function because an action is just an object, and I can grab a message by typing: `action.message`.

We handle two actions in the reducer. Switch statement checks action. type value and try to match it with any case. If any case does not address the taken type, then default case is used, and the current state is returned:

```javascript
default: {
     return state;
}
```

The first case in reducer is `ADD_MESSAGE`:

```javascript
case ADD_MESSAGE: {
    return {
        messages: [
            ...state.messages,
            action.message
        ]
    };
}
```

This case returns a new array contains the current `state `messages array (`state.messages`) and a new message received in action (`action.message`).

The second case is `REMOVE_MESSAGE`:

```javascript
case REMOVE_MESSAGE: {
    const indexToToRemove = state.messages.indexOf(action.message);

    if (indexToToRemove >= 0) {
        return {
            messages: [
                ...state.messages.splice(indexToToRemove, indexToToRemove)
            ]
        }
    } else {
        return state;
    }
}
```

It also receives a message object in action, and the reducer checks if the received message exists in the current state. If the `indexToRemove` const is equal or greater than zero, then the reducer function returns a new state containing messages without a message that should be removed. 

Otherwise, the reducer returns the current state without any mutations.

### Dispatch function

The dispatch function is used to dispatching actions to reducers. It accepts an object that specifies the action type.

The `useReducer` hook returns the dispatch function, and then you can use it in a component to mutate a state. Take a look at the example below: 

```jsx
<button onClick={() => dispatch({type: ADD_MESSAGE, message: ‘React is cool!’’})}> Add message </button>
```

A good practice is to wrap an action object by a method that returns this object. Usually, I create actions where I have declared a reducer and export them to use in components.

```javascript
export const addMessage = message => {
    return {type: ADD_MESSAGE, message};
}

export const removeMessage = message => {
    return {type: REMOVE_MESSAGE, message};
}
```

Then I can use those actions like this: 

```javascript
import {removeMessage as removeMessageAction from ‘./messagesReducer’;

dispatch(removeMessageAction(message))
```

Of course, you can pass the dispatch function to child components and use it there like this:

```javascript
<ChildComponent addMessage={message => dispatch(removeMessageAction(message))}/>
```

### Example of using the `useReducer`

Take a look at the complete example of the reducer called MessagesReducer. It’s responsible for managing messages (notifications)  in an app. 

#### The reducer

```javascript
const ADD_MESSAGE = 'ADD_MESSAGE';
const REMOVE_MESSAGE = 'REMOVE_MESSAGE';

export function MessagesReducer(state, action) {
    switch(action.type) {
        case ADD_MESSAGE: {
            return {
                messages: [
                    ...state.messages,
                    action.message
                ]
            };
        }
        case REMOVE_MESSAGE: {
            const indexToToRemove = state.messages.indexOf(action.message);

            if (indexToToRemove >= 0) {
                return {
                    messages: [
                        ...state.messages.splice(indexToToRemove, indexToToRemove)
                    ]
                }
            } else {
                return state;
            }
        }

        default: {
            return state;
        }
    }
}

export const messagesInitialState = { messages: [] }

export const addMessage = message => {
    return {type: ADD_MESSAGE, message};
}

export const removeMessage = message => {
    return {type: REMOVE_MESSAGE, message};
}
```

#### Using The reducer in the Context

In this particular example, I used that reducer in the Context. Take a look: 

```javascript
import React, { createContext, useReducer } from 'react';
import {
    MessagesReducer,
    messagesInitialState,
    addMessage as addMessageAction,
    removeMessage as removeMessageAction
 } from '../../reducers/Messages';

export const MessagesContext = createContext();

export const MessagesProvider = ({ children }) => {
    const [{ messages }, dispatch ] = useReducer(MessagesReducer, messagesInitialState);

    const removeMessage = message => dispatch(removeMessageAction(message));
    const addMessage = message => dispatch(addMessageAction(message));

    return <MessagesContext.Provider value={{
        messages,
        addMessage,
        removeMessage
    }}>
        {children}
    </MessagesContext.Provider>
};
````

You can find the complete example of using that reducer in the pull request of my personal project [here](https://github.com/Frodigo/react-apollo-storefront/pull/8/files).

## useState or useReducer?

You might ask a question: “when should I use the useReducer hook, and when the useState?”

Probably it depends on you. Just keep in mind that useReducer is better for managing complex states. 

I use the `useState` hook for managing primitive states like strings, numbers, and booleans. 

When I have to manage a more complex state, I prefer to use the `useReducer` hook.

## Summary

The useReducer hook is a good alternative for 3rd party libs like Redux and ModX. Also is an excellent option to handle non-GraphQL states in a React app connected with GraphQL API using Apollo client. 

Combining the `useReducer` hook with other React’s mechanism called `Context` allows you to manage state in your app efficiently.

## Want to go deeper?

Today I showed you only the basics of the useReducer hook. If you want to go deeper, take a look at these sources:

[https://reactjs.org/docs/hooks-reference.html#usereducer](https://reactjs.org/docs/hooks-reference.html#usereducer)

[https://blog.logrocket.com/guide-to-react-usereducer-hook/](https://blog.logrocket.com/guide-to-react-usereducer-hook/)

[https://kentcdodds.com/blog/should-i-usestate-or-usereducer](https://kentcdodds.com/blog/should-i-usestate-or-usereducer)