Mastering useReducer (1/2)
Tags: react, webdev, programming, javascript
Published: 2022-02-13 23:34:33

_This blog post takes for granted that you have some knowledge regarding React and React's Hooks._

## Managing state in React

As you probably know, React has 2 ways to manage state:

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

Both are widely used across any given React application, and although they ultimately serve the same purpose (managing state), they should be used in different situations.


### When to use `useReducer` vs `useState`

>_`useReducer` is usually preferable to `useState` when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one._ - [React's  docs](https://reactjs.org/docs/hooks-reference.html#usereducer)

As stated in the paragraph above, the `useReducer` hook should be opted in when the logic behind your state is a bit more complex or depends on the previous state.

✅ Good use cases for `useReducer`:
- Changing 1 piece of state also changes others (co-related state values);
- The state is complex and has a lot of moving parts;
- When you want/need more predictable state transitions;

## The `useReducer` hook

Now that we have some context on where to use this hook, it's time to take a closer look at it's API. 

`useReducer` it's a built in function brought by React that has 2 different signatures:

- `useReducer(reducer, initialArg);`
- `useReducer(reducer, initialArg, init);`

### `useReducer` arguments

**`reducer`**

The `reducer` as it's own name indicates, it's a function that takes some information and **reduces** it into something, and this is the place where the "magic" happens.

It takes two arguments, the current `state` and the `action` which is dispatched by the UI. By taking a given action type, a reducer will return the next piece of state, usually by deriving the previous state.

```ts
function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
  }
}
```

**``initialState``**

This argument is pretty self explanatory, it's just the state that the `useReducer` hook will start with.

**`init`**

`init` is a function that allows you do some logic around the initial state, as it will take the value you passed as `initialState` and return a "new" `initialState` based on that.

```ts
function init(initialCount) {
  return {count: initialCount};
}
```

### `useReducer` returned values

Very similar to `useState`, this hook returns an array with two values:

- The first, to show the current state;
- The second, a way to change the state, and create a re-render in the application. 

```ts
 const [state, dispatch] = useReducer(counterReducer, initialState);
```

**`state`**

This value doesn't need much explanation, it is simply the current state returned by the `useReducer` hook.

**`dispatch`**

This is a function where you can pass the the possible `actions` that you define for your `reducer` to handle. Taking the previous `counterReducer` by example, these could look like this:

```ts
function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
```

## Managing the fetching logic with the `useReducer` hook

Now that we have a better understanding of what the `useReducer` hook can do for us, it's time to get our hands dirty and make usage of this React hook to handle any given **fetching related state**.

### Fetching state

In order to use `useReducer`, you must first think what will be the state that you want to manage, these are usually all the things that you might have in a bunch of `useState` hooks, like `data`, `errorMessage`, `fetchState`, etc...
 
In this scenario, as we want to create a hook that will allow us to manage fetching logic. And as far as fetch logic goes, all the pieces you need are:

- **state**: to know if the application is `iddle`, `loading`, if the fetch was a `success` or a `failure`
- **error**: a error message in case something went wrong
- **data** : the response data

And so, now that we have our `state` structure defined, we can setup our `initialState`.

```ts
// "iddle" state because we haven't fetch anything yet!
  const initialState = {
    status: "idle",
    data: null,
    error: null,
  };
```

### Fetching reducer

#### Actions

The second step, is to create the logic that will lead to different app states. That logic lives under the `reducer` function and for us to mount that logic, we should start by thinking on the "actions" that we need to perform.

For the fetching logic, we will need the following actions:

- **FETCH**: action to be called when the request starts;
- **RESOLVE**: action to be called if the response is successful;
- **REJECT**: action to be called if the requests throws an error or the response is "invalid";

_Bear in mind that you can call these actions whatever you want, as long as they reflect what's being done and it makes sense for you._

#### State transitions

Each of these actions (`FETCH`, `RESOLVE` and `REJECT`) will lead to a state transition, thus, producing a new output (a new state). 

So now, it's just a matter of figuring out which will be the state that each of these actions will output.
 
**`FETCH`** 
![FETCH state transition](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fqmuvm34vzliyutazzrc.jpg)
 
 
---
**`RESOLVE`** 
![RESOLVE state transition](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kj8nhd7oml0vnyincksm.png)
 
 

---

**`REJECT`**
![REJECT state transition](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qv8m2wim5tjnrjqgbu19.jpg)
 
---

#### Implementing `useReducer`

With all the pseudo-code and decisions we have made above, we are now able to take advantage of `useReducer` to manage the fetching logic:

```ts
  const initialState = {
    status: "idle",
    data: null,
    error: null
  };

  function fetchReducer(currentState, action) {
    switch (action.type) {
      case "FETCH":
        return {
          ...currentState,
          status: "loading"
        };
      case "RESOLVE":
        return {
          status: "success",
          data: action.data,
          error: null
        };
      case "REJECT":
        return {
          data: null,
          status: "failure",
          error: action.error
        };
      default:
        return currentState;
    }
  }

  const [state, dispatch] = React.useReducer(fetchReducer, initialState);
}
```

---

### Fetching data

The implementation code is done, let's now check how would the code look look if we were fetching some data through our `useReducer`.

```ts
  function fetchIt() {
    // Start fetching!
    dispatch({ type: "FETCH" });
    fetch("https://www.reddit.com/r/padel.json")
      .then((response) =>
        response.json().then((result) => {
          // We got our data!
            dispatch({ type: "RESOLVE", data: result });
        })
      )
      .catch((error) => {
       // We got an error!
        dispatch({ type: "REJECT", data: error });
      });
  }

return (
    <>
      {state.status === "loading" ? <p>loading...</p> : undefined}
      {state.status === "success" ? <p>{JSON.stringify(state.data)}</p> : undefined}
      {state.status === "failure" ? <p>{JSON.stringify(state.error)}</p> : undefined}
      <button disabled={state.status === "loading"} onClick={fetchIt}>
        Fetch Data
      </button>
    </>
  );
```
## Creating `useFetchReducer` custom hook

Now, you will probably want to use this very same code to control your application's state in every place where you are performing an HTTP request. 

Luckily for us, React brings a huge [composition](https://reactjs.org/docs/composition-vs-inheritance.html) power packed in, making our life's quite simple when creating custom hooks through other existing React hooks (`useReducer` in this case).

### Extracting `useReducer` hook

The **1st step**, is to create a new file named `use-fetch-reducer.js` or whatever you wanna call it, as long and it starts with **use** (to be identified as an hook).

The **2nd step**, is to take (copy) all the code that we implemented before, and paste it inside an exported function with the name `useFetchReducer`. It should look something like this:

```js
import React from "react";

export function useFetchReducer() {
  const initialState = {
    status: "idle",
    data: null,
    error: null
  };

  function fetchReducer(currentState, action) {
    switch (action.type) {
      case "FETCH":
        return {
          ...currentState,
          status: "loading"
        };
      case "RESOLVE":
        return {
          status: "success",
          data: action.data,
          error: null
        };
      case "REJECT":
        return {
          data: null,
          status: "failure",
          error: action.error
        };
      default:
        return currentState;
    }
  }

  const [state, dispatch] = React.useReducer(fetchReducer, initialState);
}
```


The **3rd step** is to take out our `useReducer` result and return it instead, so that we can use `state` and `dispatch` in every other component:
```js
//...
return React.useReducer(fetchReducer, initialState);
```

To wrap things up, we should make this hook as "generic" as possible, so that it can satisfy the need of every component where it's being called from. In order to get there, the **4th step** passes by providing a way for consumers to set the `initialData` themselves, because it might not always start as `null`:

```js
function useFetchReducer(initialData = null) {
  const initialState = {
    status: "idle",
    data: initialData,
    error: null
  };

//...
```

### Using `useFetchReducer`

1. Import the newly created hook into your component;
2. Execute it as `const [state, dispatch] = useFetchReducer();`
3. Use it's `state` and `dispatch` as you would for the `useReducer` hook.


[Running Code](https://codesandbox.io/s/elastic-framework-9wqd9)
![useFetchReducer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y2qmb1k77g6bfv2981vr.gif)
 

## Conclusion

If your app state is becoming somewhat complex and the number of `useState` is mounting up, it might be time to do a small switch and take advantage of `useReducer` instead.

If you have decided to use `useReducer`, follow these steps:
1. Think of the **State** you want to manage;
2. Think of the **Actions** that that will trigger state transitions;
3. Think for the **State Transitions** that will happen when calling the defined set of states.

With these thought out, it's time to write your own reducer and call the `useReducer` hook.

If the logic you just created can be reused across your application, create a custom hook and enjoy 😉

---

_The 2nd part of this series will bring some **type safety** to the table, make sure to follow me on [twitter](https://twitter.com/P_FFigueiredo) if you don't want to miss it!_

_P.S. the `useFetchReducer` code was highly inspired into [David K. Piano](https://dev.to/davidkpiano)'s code, present in this great [blog post](https://dev.to/davidkpiano/no-disabling-a-button-is-not-app-logic-598i)._
_