React Advanced: learn more about useReducer through the Task Management component

May 16, 2023 1268hotness 0likes 0comments

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:

  1. use useReducer in the component to manage the task list and use useRef to create a reference to get the value of the input box.
  2. wraps the component with forwardRef so that the add task and delete task methods in the component can be called from the parent component.
  3. defines functions for add tasks and Delete tasks to update the task list.
  4. 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:

  1. ADD_TASK : add a task to the task list.
  2. DELETE_TASK : removes a task from the task list.
  3. 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 using useReducer .
  • inputRef : a reference to the input box, created with useRef .
  • 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:

effect

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.

InterServer Web Hosting and VPS

Aaron

Hello, my name is Aaron and I am a freelance front-end developer

Comments