Understanding React useReducer
Tags: javascript, react, learning
Published: 2023-12-04 03:20:19

In the previous blog post, I talked about useContext, where props can be accessed without prop drilling. However, as an app becomes more complex, it becomes challenging to manage all the states. Consequently, useReducer is often employed in conjunction with useContext.

`useReducer` is one of the React Hooks. Although I've used other Hooks for projects, I've never used useReducer, to be honest. Before delving into the usage of useReducer and useContext, I'd like to talk about what useReducer is.

Syntax
```
const [state, dispatch] = useReducer(reducer, initialArg, init?)
```

useReducer is a state management hook, similar to useState. It takes three parameters: `reducer` (a function that specifies how the state gets updated), `initialArg`, and an optional `init` (an initializer function that returns the initial state. If it's not provided, the initial state is set to initialArg). The useReducer hook returns an array containing `state` and `dispatch` (a function).

Here's an example code from React's official documentation.
If you're familiar with Redux, it might be easier to understand to integrate the idea by reading the code below.
[react.dev](https://react.dev/reference/react/useReducer)

```
import { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      return {
        name: state.name,
        age: state.age + 1
      };
    }
    case 'changed_name': {
      return {
        name: action.nextName,
        age: state.age
      };
    }
  }
  throw Error('Unknown action: ' + action.type);
}

const initialState = { name: 'Taylor', age: 42 };

export default function Form() {
  const [state, dispatch] = useReducer(reducer, initialState);

  function handleButtonClick() {
    dispatch({ type: 'incremented_age' });
  }

  function handleInputChange(e) {
    dispatch({
      type: 'changed_name',
      nextName: e.target.value
    }); 
  }

  return (
    <>
      <input
        value={state.name}
        onChange={handleInputChange}
      />
      <button onClick={handleButtonClick}>
        Increment age
      </button>
      <p>Hello, {state.name}. You are {state.age}.</p>
    </>
  );
}
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rg7avo5r3riv3b8pcxyk.jpeg)

This is a form where the age is incremented when a user clicks on the "Increment age" button, and the name is displayed as a user enters it in the form.

Now, let's break this down.

**Reducer Function**

```
function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      return {
        name: state.name,
        age: state.age + 1
      };
    }
    case 'changed_name': {
      return {
        name: action.nextName,
        age: state.age
      };
    }
  }
  throw Error('Unknown action: ' + action.type);
}
```

The reducer function takes in `state` and `action`, representing the current state and the type of action the user has "chosen," respectively. It returns the next state depending on the action type, following the convention of using `switch` for readability. 
In this example, when the "Increment age" button is clicked, and `incremented_age` is called, the reducer function returns this object: `{ name: state.name, age: state.age + 1 };`

According to the reference, it's recommended to wrap each case block in **{** and **}** curly braces to prevent variables declared inside different cases from clashing with each other.

Additionally, it's important to note that `state` is immutable, meaning we should avoid modifying any objects or arrays within it. To achieve this, a spread operator is used to create a shallow copy.

```
function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      // Return a new object
      return {
        ...state, // a spread operator
        age: state.age + 1
      };
    }
```

**useReducer()**
```
const initialState = { name: 'Taylor', age: 42 };

export default function Form() {
  const [state, dispatch] = useReducer(reducer, initialState);

OR

  const [state, dispatch] = useReducer(reducer, { name: 'Taylor', age: 42 });
```
_state_: the current state with the initial value of { name: 'Taylor', age: 42 }
_dispatch_: a function called depending on the user's action

When a user enters a new name:

```
function handleInputChange(e) {
  dispatch({
    type: 'changed_name',
    nextName: e.target.value
  });
}

<input
  value={state.name}
  onChange={handleInputChange}
/>
```

When a user clicks on the button:

```
function handleButtonClick() {
  dispatch({ type: 'incremented_age' });
}

<button onClick={handleButtonClick}>
  Increment age
</button>
```

React will:
1. Store the next state
2. Render your component with it
3. Update the UI

Whether we should use the useState or the useReducer is discussed here: 
[Comparing useState and useReducer](https://react.dev/learn/extracting-state-logic-into-a-reducer#comparing-usestate-and-usereducer)

This blog focuses on the usage of useReducer and useContext, so I'll skip that discussion.

Now that I understand the useReducer, it's time to explore why using both useReducer and useContext can be dynamic.