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
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 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:
steps
We need to create a simple react app with the following steps:
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 storedlib.ts
: this is where your presentational logic is storedindex.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 which in later posts I will dive into
Now let's fill the code:
// 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;
// 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");
}
};
// 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>
);
};
Top comments (0)