React Reducers
Tags: webdev, react, typescript
Published: 2023-10-24 19:23:23



When it comes to reducers, you may often think that it is 
related to the great `Redux` library, but it is originally comes from the `React` library itself.


We all have seen components that does one or two clicks, and they do many state changes, handling many state changes with `useState` can sometimes be tedious, we want to introduce another approach in handling multi-state changes inside your component.


> I am not advising into stopping the usage of `useState` or the state management solutions, use this solution when the state is only the component's concern and we have many state changes ahead, for this case, you can consolidate all the state update logic outside your component in a single function, called a reducer.

A reducer is a way To reduce this complexity and keep all your logic in one easy-to-access place, you can move that state logic into a single function outside your component.

## How to call it
```tsx
import { useReducer } from "react";

const Componet = () => {

  const [state, dispatch] = useReducer<DispatcherType>(reducer, {
    tasks: [],
  });
....
}
```
As you can see in the `DispatcherType` is function type that takes the `currentState` and the dispatched action as arguments, and returns the `newState`.

### Dispatch:

The dispatch function by convention has `type` prop that infers the event name, it is advised that the event name is in [snake_case](https://en.wikipedia.org/wiki/Snake_case) and with with past tense

As can be seen here example:
```
dispatch({
  // specific to component
  type: 'what_happened',
  // other fields go here
});
```
> payload contains the event data that host the state changes, as recommended


## creating a Project 


> TLDR Here is the running code example:

{% replit https://replit.com/@IbrahimShamma99/react-native-reducers#src/App.tsx %}


### steps

We need to create a simple react app with the following steps:

```bash
npx create-react-app my-app --template typescript
cd ./my-app
```

Once the app is created, we need to create `component` called `task-list` that will encapsulate the UI/Logic/Types and everything related to our simple app

```
mkdir ./src/components
mkdir ./src/components/task-list

touch ./src/components/task-list/lib.ts
touch ./src/components/task-list/types.ts
touch ./src/components/task-list/index.tsx
```

We have in the component 3 distinct files:

- `types.ts`: this will host the types of the component logic and state, this is great for newcomers to your code to understand what is actions in the ui and what is state being stored

- `lib.ts`: this is where your presentational logic is stored

- `index.ts`: Typical UI elements 

> This component setup is meant for simplicity, if you want a better setup for your working team, introduce to them [FSD](https://feature-sliced.design/) which in later posts I will dive into

Now let's fill the code:


```ts
// types.ts

export type Task = {
  id: string;
  name: string;
};

export type State = {
  tasks: Task[];
};

export type TaskAdded = {
  type: "task_added";
  payload: {
    task: string;
  };
};

export type TaskRemoved = {
  type: "task_removed";
  payload: {
    id: string;
  };
};

export type Action = TaskRemoved | TaskAdded;

export type Dispatch = (state: State, action: Action) => State;

```


```ts
// lib.ts
import { Action, State } from "./types";
import { v4 } from "uuid";

export const reducer = (state: State, action: Action) => {
  console.log("state", state);
  console.log("action", action);

  switch (action.type) {
    case "task_added":
      return {
        tasks: [
          ...state.tasks,
          {
            id: v4(),
            name: action.payload.task,
          },
        ],
      };
    case "task_removed":
      return {
        tasks: state.tasks.filter((a) => a.id !== action.payload.id),
      };
    default:
      throw Error("Event Not Implemented");
  }
};
```

```tsx
// index.tsx

import { useState, useReducer } from "react";
import { reducer } from "./lib";
import { Dispatch } from "./types";

export const TaskList = () => {
  const [state, dispatch] = useReducer<Dispatch>(reducer, {
    tasks: [],
  });
  const { tasks } = state;
  const [currentInput, setCurrentInput] = useState<string>("");

  return (
    <div>
      <form>
        <input
          onChage={(e: any) => setCurrentInput(e.target.value)}
          placeholder="task"
          type="text"></input>
        <button
          type="button"
          onClick={() => {
            dispatch({
              type: "task_added",
              payload: {
                task: currentInput,
              },
            });
            setCurrentInput("");
          }}>
          submit
        </button>
      </form>
      {tasks.map((task, index) => (
        <div key={index}>
          <div>task {task.name}</div>
          <button
            type="button"
            onClick={() => {
              dispatch({
                type: "task_removed",
                payload: {
                  id: task.id,
                },
              });
            }}>
            delete
          </button>
        </div>
      ))}
    </div>
  );
};
```