Simplify state with reducer
Tags: react
Published: 2023-03-08 08:48:18

## TL;DR

React state is good to store a reactive data. When the data changes, the UI will re-render and shwoing the latest update.

Complex UI also need more than one state. Possibly more than three.

To simplify manage the state, instead of updating the state one by one using its setter function, it's better to put all states in one place and update it according to event occured using `dispatch()` function.

```jsx
// Example of end result

// From this
setLoading(true);
setVisible(true);
setStatus('error');

// To this
dispatch({ type: 'loading_started' });
dispatch({ type: 'status_changed', payload: 'success' });
dispatch({ type: 'visible_toggled' });
```

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

## Introduction

We love state because it makes our apps interactive with less of code, but managing lots of state can be tricky.

Imagine we have a form component. It's very common case that a form can have mutiple state inside of it. For example, a form may have states for loading, status, form submit, etc.

The reason why we do this because we want to tell the users what happen to the current process.

With so many states it is a good advice if you put all states inside one place. A large, maintainable and consistent shape.

And now, we are going to explore one of built-in React API to create our own shape to simplify managing the states.

## The Distraction

A form commonly use an alert component to inform the users that their data is successfully processed or not.

![A form state changed to success](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mf9ztk2tb5q403oqz3ox.png)
*An alert component appears with different style according to the `status` state.*

...and your code basically do something like this:

```jsx
setStatus('success');
```

Theres nothing wrong with that code above. But, could you imagine managing many states inside a component? BTW, let's add more complexity to the UI.

![Loading changed to false](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ybb6wgv5io4bszkriscm.png)

Here's what the changes does. When user clicks the submit button, the button text change to "Loading...". Indicating that the data is being processed. The button is disabled to prevent user clicks multiple times until the process done.

---

So far we have two states. The `status` and `loading`. Can you spot the "distraction"? I think not yet.

Some particular apps using an overlay to display the form to minimize the page transition which will improve UX. The form should appear when user click a "trigger" and can be closed.

![User clicks Add Product button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rv53ksxjtkejdaskpwc8.png)

Now if we combining all states we should have `status`, `loading` and `visible`.

Let's assume we have declared those states inside our component and to change the value for each state, you use the setter function.

```jsx
setVisible(true);
setLoading(true);
setStatus('warning');
```

As your UI getting more complex, you will have more states. More states meaning more setter functions and that's the first distraction.

With so many setter functions, the behaviour of consistency to update the state a little bit lost because to update a state you need a specific setter function.

---

If using multiple setter function in a single component isn't make you distracted, let's take a look to [maintainability](https://en.wikipedia.org/wiki/Maintainability).

Imagine you develop a text editor for eCommerce website. So the seller can format the description part of a product to provide better information.

![Text editor](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jjhlwtudz2vpfxouxyu2.png)
*Example of simple text editor*

From image above we possibly have four states inside the text editor. Which is:

- `formatter`
- `mode`
- `showOptions`
- `letterCount`

Although this is very simple text editor, you need to extract the UI into components based on its functionality and this is the problem come from.

```jsx
// Just example :)
function TextEditor() {
  return (
    <div>
      <Formatter />
      <ModeSwitch />
      <Options />
      <ContentBody >
      <LetterCount />
    </div>
  );
}
```

Some of components need sharing states to act normally. For example the `<Formatter />` may use the `mode` state because the formatter appear based on what current mode is being used. If user switch to Markdown, the formatter should disappear and vice versa.

The question is how do we share the states through all components?

The answer is very simple. Yes, using the Context. But, how? Like this?

```jsx
function TextEditor() {
  // state declarations here

  return (
    <div>
      <TextEditorProvider
        formatter={formatter}
        mode={mode}
        showOptions={showOptions}
        letterCount={letterCount}
      >
        <Formatter />
        <ModeSwitch />
        <Options />
        <ContentBody >
        <LetterCount />
      </TextEditorProvider>
    </div>
  );
}
```
And inside the `<Formatter />` component, use the `mode` state.

```jsx
function Formatter() {
  const state = useContext(TextEditorContext);

  const [mode] = state.mode;

  return mode === 'editor' <div /> : null;
}
```

Another problem is the props of `<TextEditorProvider />` grow exponentially as you add more state to the `<TextEditor />` component.

So, do we have better solution? Well, of course, yes.

## The Flux Architecture

We are not going to discuss Flux as a whole because it's a huge topic. What we're gonna do is to getting know about the main idea of Flux Architecture.

Flux Architecture or Flux is a pattern to manage states that live inside your component.

It's not a framework, but something like idealogy similar to [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) pattern. Even though it has minimal library to simplify our work.

Flux has four main parts:

- Dispatcher
- Stores
- Actions
- Views

![Data in a Flux application flows in a single direction](https://facebook.github.io/flux/img/overview/flux-simple-f8-diagram-with-client-action-1300w.png)
*https://facebook.github.io/flux/docs/in-depth-overview*

### Dispatcher

A dispatcher is a central place to updating the state. It is similar with setter function of React state.

```jsx
const [state, setState] = useState();

// This is the dispatcher.
setState();
```

> **Note:**
>
> That code above is not the actual flux dispatcher.

### Stores

A store is what holds the data of an application. So, instead of expose the data publicly, it's wrap the data in the store and perform operation inside it (via dispatcher).

You can see [here](https://github.com/facebook/flux/blob/main/examples/flux-flow/src/AppStore.js) for an example.


### Actions

An action is a simple object that describe of what condition or event that being occurred. Action is used by dispatcher to determine what operation needs to be performed.

```js
const MODE_SWITCHED_TO_MARKDOWN = {
  type: 'mode-switched',
  payload: 'markdown'
};

const MODE_SWITCHED_TO_EDITOR = {
  type: 'mode-switched',
  payload: 'editor'
};

// Dispatch an action
dispatcher.dispatch(MODE_SWITCHED_TO_MARKDOWN);
```

If an action need to pass a data or you want the action dynamic, you can use **Action Creator**. Action Creator is a simple function that produce an action.

```js
const switchMode = (mode) => ({ type: 'mode_switched', payload: mode })

dispatcher.dispatch(switchMode('html'));
```

We have to remember that the most important part is `type` because we need this information to identify what operation should be performed.

---

The why Flux pattern can provide a better solution rather than raw React state because with Flux we can do many things. Not only holds a data.

- Separation of concerns
- Easier Debugging
- Code predictability

The list above is some of Flux benefits.

### Views

Nothing special with views. Views is component you made that may use the store or dispatch an action.

```jsx
// Example of component that dispatch an action
function ModeSwitch() {
  return <Switcher
    onSwitch={() => {
      dispatcher.dispatch(MODE_SWITCHED_TO_EDITOR);
    }}
  />
}
```

---

See?  Now it looks maintainable and consistent because we don't need to use specific setter function for each state.

You might be feel this approach little bit confused because I only show the main idea and use part of the code. But, once you got the main idea I ensure you will love this approach.

And the reason why I only show the main idea because Flux is currently in maintenance mode, but the idea still live and adopted by `React.useReducer` and `Redux`.

To get more information about Flux, go to their official website [here](https://facebook.github.io/flux/).

## Combining Context And Flux

Flux by defult is shareable between all views. That means we don't need to pass the Flux data into the Context.

Since Flux is in maintenance mode, it's not good practice to use Flux anymore. But, follow the main idea is a good deal, tho.

> Note:
> The maintenance mode is refers to [Flux library](https://www.npmjs.com/package/flux) not the main idea.

---

Is it possible to implement the Flux idea and share it between components through Context? The answer is yes.

Let's implement the Context first.

```jsx
// text-editor-context.jsx
import React from 'react';

const TextEditorContext = React.createContext({});

export const TextEditorProvider = (props) => {
  <TextEditorContext.Provider value={{ store: props.store, dispatch: props.dispatch }}>
    {props.children}
  </TextEditorContext.Provider>
};

export default TextEditorContext;
```

The Context role is act like a bridge that connects all components to consume shareable state. Just it.

So, how implement the Flux? since the library itself is not continued by the maintainer.

Therefore, please welcome...

### React.useReducer

What is `React.useReducer`? Well, because you already know about Flux. It is simplified Flux without shareable ability. Therefore, we use Context to make it shareable.

`React.useReducer` or simplified called reducer is slightly different compared to Flux.

Flux is require you to create a class that extends `ReduceStore` from the Flux library. While `React.useReducer` is a hook and takes two arguments to operates. The `reducer` and `inititalState`.

> **Note:**
> Read more about React.useState [here](https://reactjs.org/docs/hooks-reference.html#usereducer).

With `React.useState` we can create a Flux-like state and should answer the question from the previous section.

```jsx
// Example of reducer.
const initialState = {
  formatter: null,
  mode: 'editor',
  showOptions: false,
  letterCount: 0,
};

// Actions
export const switchMode = (mode) => {
  return {
    type: 'mode-switched', 
    payload: mode,
  };
};
// Rest of actions...

const reducer = (state, action) => {
  switch (action.type) {
    case 'mode-switched':
      return { ...state, mode: action.payload }
    case 'formatter-changed':
      return { ...state, formatter: action.payload };
    case 'options-toggled':
      return { ...state, showOptions: !state.showOptions };
    case 'letter-count-increased':
      return { ...state, letterCount: state.letterCount + 1};
    case 'letter-count-decreased':
      return { ...state, letterCount: state.letterCount - 1};
    default:
      throw new TypeError('Unidentified Type Given.');
  }
}

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

  return (
    <div>
      <TextEditorProvider state={state} dispatch={dispatch}>
        <Formatter />
        <ModeSwitch />
        <Options />
        <ContentBody >
        <LetterCount />
      </TextEditorProvider>
    </div>
  );
}
```
And now all components wrapped inside `<TextEditorProvider />` should be able to access the `state` object and `dispatch` API.

## The Final Result

After all, we are going to combine all solutions.

The inconsistency problem has been solved from code above. Theres no specific setter function. Theres only `dispatch()` function to update specified state.

The maintainability problem is also solved because if you want add new feature or states, you are not polluting the `<TextEditorProvider />`.

Instead of adding props, you just need to update the `initialState` and adding some actions or action creators.

And now you can use the states across all components...

```jsx
function ModeSwitch() {
  const { dispatch } = useContext(TextEditorContext);

  return <Switcher
    onSwitch={() => {
      dispatch(switchMode('markdown'));
    }}
  />
}
```

```jsx
// Theres no need to destruct the array :)
function Formatter() {
  const { state } = useContext(TextEditorContext);

  return state.mode === 'editor' <div /> : null;
}
```

You can even make the context simpler using a custom hook.

```jsx
const useStore = () => {
  const store = useContext(TextEditorContext);

  return store;
};

// Use it like this
const { state, dispatch } = useStore();
```

## Conclusion

Managing state can be tricky for complex application. Using combination of state and context is good, but we have better solution by using reducer.

Reducer is ensure us to update the state only using the `dispatch()` API. Minimizing distraction when using setter function, improve maintainability and consistency.

The Flux pattern is every useful for large application. Separation of concern, easy to debug and predictability. All of those features are needed by long-runnung application.

Luckly, reducer is already implement this pattern. Even though with simplification, but it still worth.

By combining context and reducer, you got Flux with simpler approach. Awesome!

FIY, one of top library that use Flux idea is [Redux](https://redux.js.org). And this is my favorite React state management.

<a href="https://www.buymeacoffee.com/thexdev"><img src="https://img.buymeacoffee.com/button-api/?text=Buy me a macbook&emoji=💻&slug=thexdev&button_colour=FFDD00&font_colour=000000&font_family=Poppins&outline_colour=000000&coffee_colour=ffffff"></a>