in the article React developer essential skills: master useReducer foundation and application , we discussed the basic knowledge of useReducer in React. In this article, we will discuss the advanced techniques of useReducer. This article will show you how to flexibly use useReducer to manage complex states within and between components in React programs through a more complex "task control component"-- the core operation component of task processing in TODO applications.
implementation ideas
in TODO applications, the core operation is the various processing logic of "task", including "add task", "delete task", "filter task", "search task" and so on. To make it easier to understand, we only implement the "add task" and "delete task" logic.
at the same time, in order to facilitate us to later maintain and expand the function and logic of the "task" operation, we should package it into a separate component. In addition, the "add task" and "delete task" functions of the component should also be exposed for the parent component to call.
subcomponents TaskControl
this subcomponent should contain the following functions:
- use
useReducer
in the component to manage the task list and useuseRef
to create a reference to get the value of the input box. - wraps the component with
forwardRef
so that the add task and delete task methods in the component can be called from the parent component. - defines functions for add tasks and Delete tasks to update the task list.
- use
useImperativeHandle
to expose the add task and delete task functions to the parent component for calling.
Let's take a look at the specific code implementation:
import React, {
useReducer,
useRef,
forwardRef,
useImperativeHandle,
} from "react";
// define the reducer function to update the task list
function reducer(state, action) {
switch (action.type) {
// add Task
case "ADD_TASK":
return [...state, action.payload];
// Delete task
case "DELETE_TASK":
const newTasks = state.filter(
(task) => task.id !== Number(action.payload.id)
);
return newTasks;
// if action.type does not support it, throw an error
default:
throw new Error(`Unsupported action type: ${action.type}`);
}
}
// use forwardRef to wrap TaskControl components
const TaskControl = forwardRef((props, ref) => {
// use useReducer to manage task lists
const [tasks, dispatch] = useReducer(reducer, []);
// create an inputRef reference to get the value of the input box
const inputRef = useRef(null);
// create an idInputRef reference to get the value of the id input box
const idInputRef = useRef(null);
// add the function of the task
function addTask() {
// get the task name from input
const name = inputRef.current.value.trim();
if (name === "") return;
// add a new task to the task list
dispatch({
type: "ADD_TASK",
payload: {
name,
id: Date.now(),
completed: false,
},
});
// clear the contents of the input
inputRef.current.value = "";
// refocus on the input element
inputRef.current.focus();
}
// delete the function of the task
function deleteTask() {
// Delete a task according to its ID
dispatch({
type: "DELETE_TASK",
payload: { id: idInputRef.current.value.trim() },
});
}
// use useImperativeHandle to expose addTask and deleteTask functions to the parent component
useImperativeHandle(ref, () => {
return { addTask, deleteTask };
});
// rendering component
return (
<div>
{/ * input box * /}
<input type="text" ref={inputRef} placeholder="Enter task name" />
<input type="text" ref={idInputRef} placeholder="Enter task id" />
{/ * Task list * /}
<ul>
{tasks.map((task) => (
<li key={task.id}>
{task.name} (id: {task.id})
</li>
))}
</ul>
</div>
);
});
export default TaskControl;
In the code
above, we define a reducer
function to manage the state of the task list. This function takes two parameters: the current state and an action object. The reducer function updates the status based on the action type. If the action type is not supported, an error is thrown.
in this reducer function, there are three possible action types:
-
ADD_TASK
: add a task to the task list. -
DELETE_TASK
: removes a task from the task list. - other types: an error is thrown to indicate that this type of action is not supported.
then we use the forwardRef
function to wrap the component. forwardRef
the function converts one component to another so that the component's methods can be called from the parent component. This function takes a function as an argument and returns a new component.
the whole TaskControl
component contains the following states and methods:
-
tasks
: the status of the task list is managed usinguseReducer
. -
inputRef
: a reference to the input box, created withuseRef
. -
addTask
: the method to add a task to the task list. -
deleteTask
: the method to delete a task from the task list based on its ID.
After
, we expose the addTask
and deleteTask
methods of the component through useImperativeHandle
. When the parent component calls these two methods, we can add and delete tasks.
next we render the component in the parent component and call the two methods it exposes.
parent component Parent
import React, { useRef } from "react";
import TaskControl from "../components/TaskControl";
export default function Todo(): JSX.Element {
// create a ref object to reference an instance of the TaskControl component
const taskControlRef = useRef(null);
// Click the event handler of the "Add Task" button
const handleAddTask = () => {
// call the addTask method on the TaskControl instance through the ref object
taskControlRef?.current.addTask();
};
// Click the event handler of the "Delete Task" button
const handleDeleteTask = () => {
// call the deleteTask method on the TaskControl instance through the ref object
taskControlRef.current.deleteTask();
};
return (
<div>
{/ * pass taskControlRef as a ref attribute to the TaskControl component * /}
<TaskControl ref={taskControlRef} />
<button onClick={handleAddTask}>Add Task</button>
<button onClick={handleDeleteTask}>Delete Task</button>
</div>
);
}
in the above code, we invoke the methods exposed in the subcomponent TaskControl through ref. When we click the "Add Task" and "Delete Task" buttons on the page, we add and delete the task list by calling the methods in the subcomponents.
the specific effects are as follows:
Summary
in the TaskControl component, we use useReducer to control the status of tasks within the component. In the dispatch function, we can enrich the logic of each small reducer function according to the input parameters by passing additional parameters to payload.
flexible use of useReducer allows us to organize well-structured code logic and improve the maintainability and readability of our code. Therefore, in addition to using Redux to manage component state, it is also necessary for us to be proficient in useReducer.