React useReducer for dummies
Tags: react, javascript, dummies, hooks
Published: 2021-07-24 13:59:42

So you finally start your React journey or you're just converting your apps to use functional components and you come across the `useReducer` hook but can't really wrap your head around it? I'll show you how in few minutes. Read on...

The `useReducer` hook is best used when your component has multiple states, to avoid multiple calls to `useState`. It is also useful when you want to avoid managing multiple callbacks. I'll show example of each use case.

### Example with `useState`
Consider the following component:
```javascript
//ES6 and JSX
import React, {useState} from 'react';

function MyBeautifulForm() {
    const [username, setUsername] = useState('');
    const [password, setpassword] = useState('');
    const [email, setEmail] = useState('');
    const [age, setAge] = useState(0);
    const [height, setHeight] = useState(0);
    const [acceptTerms, setAcceptTerms] = useState(false);

    function handleFormSubmit(event) {
        event.preventDefault();
        // processForm();
    }

    return (
        <form onSubmit={handleFormSubmit}>
            <input type="text" name="username" value={username} onChange={(e) => setUsername(e.target.value)} />
            <input type="password" name="password" value={password} onChange={(e) => setPassword(e.target.value)} />
            <input type="email" name="email" value={email} onChange={(e) => setEmail(e.target.value)} />
            <input type="number" name="age" value={age} onChange={(e) => setAge(e.target.value)} />
            <input type="number" name="height" value={height} onChange={(e) => setHeight(e.target.value)} />
            <input type="checkbox" name="terms" checked={acceptTerms ? 'checked' : ''} onChange={(e) => setAcceptTerms(e.target.checked)} />
            <input type="submit" name="submit" value="Submit" />
        </form>
    )
}
```

That's a whole lot of `useState`s to maintain. If our form continues to grow by adding more fields, we would have even more states to maintain and this would someday grow into a big mess. So how do we take care of this?

Your initial thought might be to turn all the initial states to a single state object and pass it to a single `useState` call. While this is going to work, we should remember that calling `setState` in `function components` replaces the state rather than merging as in `class components`. States can be lost or overridden unintentionally if updated this way most especially if we're coming from class components.

This is where the `useReducer` hook shines. Let's see how we can use it to simplify the previous code:

### Example with `useReducer`
```javascript
//ES6 and JSX
import React, {/*useState,*/ useReducer} from 'react';

function reducer(state, action) {
    switch(action.type) {
        case 'USERNAME':
            return {...state, username: action.payload};
        case 'PASSWORD':
            return {...state, password: action.payload};
        ...
        ...
        default:
            return state;
    }
}

function MyBeautifulForm() {
    const initialState = {
        username: '',
        password: '',
        email: '',
        age: 0,
        height: 0,
        acceptTerms: false
    }  
    // const [formState, setFormState] = useState(initialState); // we will come bct to this later

    const [state, dispatch] = useReducer(reducer, initialState);

    function handleFormSubmit(event) {
        event.preventDefault();
        // processForm();
    }

    return (
        <form onSubmit={handleFormSubmit}>
            <input type="text" name="username" value={state.username} onChange={(e) => dispatch({type: 'USERNAME', payload: e.target.value})} />
            <input type="password" name="password" value={state.password} onChange={(e) => dispatch({type: 'PASSWORD', payload: e.target.value})} />
            <input type="email" name="email" value={state.email} onChange={(e) => dispatch({type: 'EMAIL', payload: e.target.value})} />
            <input type="number" name="age" value={state.age} onChange={(e) => dispatch({type: 'AGE', payload: e.target.value})} />
            <input type="number" name="height" value={state.height} onChange={(e) => dispatch({type: 'HEIGHT', payload: e.target.value})} />
            <input type="checkbox" name="terms" checked={state.acceptTerms ? 'checked' : ''} onChange={(e) => dispatch({type: 'TERMS', payload: e.target.checked})} />
            <input type="submit" name="submit" value="Submit" />
        </form>
    )
}
```

And now, we have a single state object (initialState) and a single state updater function (reducer).

Let's discuss what just happened:

**useState**
You may have been wondering what we mean when we write:
`const [something, setSomething] = useState(somethingElse);`
The above line of code used something known as `javascript array destructuring`. The two constants (something and setSomething) is set to the first two values of the array returned by the call to useState.

Consider the following codes:
```javascript
// const [fruit, setFruit] = useState('Apple');
let result = useState('Apple');//apple is the initial state
// you can pass null to the function
// if your component does not have an initial state
// console.log(result) // ['Apple', functionToSetAnotherName];

// later
let state = result[0];
let updater = result[1];

// let fruit = result[0];
// let setFruit = result[1];

//Well, this is not worth it...
//array destructuring is short and quite simple to use.
```

You may declare a state as follows: ```const [car, name] = useState('Toyota');``` if you want to create unnecessary headache for yourself and other team members.

Using `[something, setSomething]` is the adopted way of doing this, so you have to follow the pattern.

Whenever your state changes and you want to update it, the only way to do that is by using the second item returned by the `setState` function. I call it the `state updater`. This way, you are sure that your state will always maintain the correct value. Always use the function to update your state and avoid changing the value directly (mutation).

**useReducer**
The process of setting or updating a state with the useReducer may not be as straightforward as with useState but it is more elegant. The steps are as follows:

1. Declare your `intialState` object
2. Declare your reducer function
3. Declare your state with the ```const [state, dispatch] = useReducer(reducer, initialState)``` syntax
4. Dispatch your actions to the reducer function with the `dispatch` function (this would have been the `state updater` function in `useState`, but since we do not keep a separate state for each component, we have to send the update request to the reducer)
5. Update your state within the reducer function using the type and the payload information provided by the dispatcher.
6. The return value after the update is the current state which ensures that our state is up to date

What you gain from this is simplicity as you do not need to pass different callbacks around - you only pass a single `dispatch` which returns only one value (currentState)

## Finally
Let's demonstrate how to use `useState` with a single state object.

### Avoid multiple calls to `useState`
```javascript
//ES6 and JSX
import React, {useState} from 'react';

function MyBeautifulForm() {
    const initialState = {
        username: '',
        password: '',
        email: '',
        age: 0,
        height: 0,
        acceptTerms: false
    }  
    const [formState, setFormState] = useState(initialState);

    function updateState(state) {
        setFormState(formState => {...formState, ...state});
    }

    function handleFormSubmit(event) {
        event.preventDefault();
        // processForm();
    }

    return (
        <form onSubmit={handleFormSubmit}>
            <input type="text" name="username" value={state.username} onChange={(e) => updateState({username: e.target.value})} />
            <input type="password" name="password" value={state.password} onChange={(e) => updateState({password: e.target.value})} />
            <input type="email" name="email" value={state.email} onChange={(e) => updateState({email: e.target.value})} />
            <input type="number" name="age" value={state.age} onChange={(e) => updateState({age: e.target.value})} />
            <input type="number" name="height" value={state.height} onChange={(e) => updateState({height: e.target.value})} />
            <input type="checkbox" name="terms" checked={state.acceptTerms ? 'checked' : ''} onChange={(e) => updateState({acceptTerms: e.target.checked})} />
            <input type="submit" name="submit" value="Submit" />
        </form>
    )
}
```

In a simple way, the code above can be used to achieve the same result as the previous example with `useReducer`. Although, for some use cases, you might find one to be more elegant than the other. The aim of this article is to explain the basic usage of the `useReducer` hook and not to promote one at the expense of the other.

## Conclusion
We have seen how the `useReducer` hook can be used to combine multiple states into a single state object and updated with the reducer function using the information provided by the dispatch function. I will answer any question at the comment section.

Thanks ;)

If you're still reading, you can check out my clone of dev.to with React.js at [Dev to Blog](https://maswerdna.github.com/dev-to-blog). The source code is available at the public repo [Github Link](https://www.github.com/maswerdna/dev-to-blog)