Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
April 23, 2023 04:26 pm GMT

Simplifying state management with useReducer hook

State management is a fundamental aspect of React. React provides different native ways to manage and maintain state. One of the most commonly used way to manage a state is useState hook.

When you start your journey of learning hooks in React, useState will probably be your first destination. The useState hook provides a simple API to manage a component state. Below is an example of useState hook.

const [count, setCount] = useState(0)

The useState hook returns two variables:

  1. count - an actual state
  2. setCount - a state updater function

A React component generally contains the logic that returns some JSX i.e. the logic to render UI. When you add state management to the component then it contains both logic for rendering a UI and managing the state. When a component gets big, it becomes quite challenging to maintain both state management and UI rendering logic. Although, this works totally fine still a React component should separate the UI and state management logic.

Lets look at the below example:

import { useState } from "react";import { v4 as uuidv4 } from "uuid";const ListState = () => {  const defaultTodo = [    { id: uuidv4(), title: "\"Write a new blog\", isComplete: false },"    { id: uuidv4(), title: "\"Read two pages of Rework\", isComplete: true },"    { id: uuidv4(), title: "\"Organize playlist\", isComplete: false },"  ];  const [todo, setTodo] = useState(defaultTodo);  const [item, setItem] = useState("");  // Marks a given todo as complete or incomplete  const handleTodoChange = (id) => {    const updatedTodos = todo.map((item) => {      if (item.id === id) {        return { ...item, isComplete: !item.isComplete };      }      return item;    });    setTodo(updatedTodos);  };  // Adds a new todo item to the list  const handleAddItem = (e) => {    e.preventDefault();    const updatedTodos = [      ...todo,      { id: uuidv4(), title: "item, isComplete: false },"    ];    setTodo(updatedTodos);    setItem("");  };  // Removes todo item from the list based on given ID  const handleDeleteItem = (id) => {    const updatedTodos = todo.filter((item) => item.id !== id);    setTodo(updatedTodos);  };  return (    <div className="container">      <div className="new-todo">        <input          placeholder="Add New Item"          value={item}          onChange={(e) => setItem(e.target.value)}          className="todo-input"        />        <button onClick={handleAddItem} className="add-todo">          Add        </button>      </div>      <ul className="todo-list">        {todo.length > 0 ? (          todo.map((item) => (            <li key={item.id} className="list-item">              <input                type="checkbox"                checked={item.isComplete}                onChange={() => handleTodoChange(item.id)}              />              <p>{item.title}</p>              <button                onClick={() => handleDeleteItem(item.id)}                className="delete-todo"              >                Delete              </button>            </li>          ))        ) : (          <p>List is empty</p>        )}      </ul>    </div>  );};export default ListState;

The above code renders a simple Todo list where a user can do the following things:

  1. Add a new todo item
  2. Delete a todo item
  3. Mark a todo as complete/incomplete.

The above component contains both the logic for handling state and rendering JSX.

React provides a native hook called useReducer using which we can separate the logic for UI and state management. A useReducer hook is an alternative to useState hook.

What is useReducer?

The useReducer hook allows you to add state management to your component similar to useState hook. The only difference is that it does this by using a reducer pattern. The API for useReducer hook is as follows:

const [state, dispatch] = useReducer(reducerFn, initialState)

The useReducer hook takes two parameters:

  1. A reducer function that contains the logic for state management.
  2. An initial state

The useReducer hook returns two variables:

  1. The current state
  2. A dispatch function which when called invokes the reducer function to update the state.

Lets learn to use this hook by refactoring the above Todo list component.

import { useState, useReducer } from "react";import { ListReducerFn } from "../utils/functions";import { defaultTodo } from "../utils/defaults";const ListReducer = () => {  const [todo, dispatch] = useReducer(ListReducerFn, defaultTodo);  const [item, setItem] = useState("");  // Marks a given todo as complete or incomplete  const handleTodoChange = (id) =>    dispatch({ type: "UPDATE", payload: { id } });  // Adds a new todo item to the list  const handleAddItem = (e) => {    e.preventDefault();    dispatch({ type: "ADD", payload: { item } });    setItem("");  };  // Removes todo item from the list based on given ID  const handleDeleteItem = (id) =>    dispatch({ type: "DELETE", payload: { id } });  return (    <div className="container">      <div className="new-todo">        <input          placeholder="Add New Item"          value={item}          onChange={(e) => setItem(e.target.value)}          className="todo-input"        />        <button onClick={handleAddItem} className="add-todo">          Add        </button>      </div>      <ul className="todo-list">        {todo.length > 0 ? (          todo.map((item) => (            <li key={item.id} className="list-item">              <input                type="checkbox"                checked={item.isComplete}                onChange={() => handleTodoChange(item.id)}              />              <p>{item.title}</p>              <button                onClick={() => handleDeleteItem(item.id)}                className="delete-todo"              >                Delete              </button>            </li>          ))        ) : (          <p>List is empty</p>        )}      </ul>    </div>  );};export default ListReducer;
// utils/defaults.jsimport { v4 as uuidv4 } from "uuid";export const defaultTodo = [  { id: uuidv4(), title: "Write a new blog", isComplete: false },  { id: uuidv4(), title: "Read two pages of Rework", isComplete: true },  { id: uuidv4(), title: "Organize playlist", isComplete: false },];
// utils/functions.jsimport { v4 as uuidv4 } from "uuid";export const ListReducerFn = (state, action) => {  switch (action.type) {    case "ADD": {      const updatedTodos = [        ...state,        { id: uuidv4(), title: action.payload.item, isComplete: false },      ];      return updatedTodos;    }    case "DELETE": {      const updatedTodos = state.filter(        (item) => item.id !== action.payload.id      );      return updatedTodos;    }    case "UPDATE": {      const updatedTodos = state.map((item) => {        if (item.id === action.payload.id) {          return { ...item, isComplete: !item.isComplete };        }        return item;      });      return updatedTodos;    }    default: {      throw new Error(`Unsupported action: ${action.type}`);    }  }};

As you can see, the JSX for both versions of Todo list component is same. The only difference is in the logic for handling state management. Lets discuss the differences between the Todo list component that uses useState and useReducer hooks.

Here is a function to add a new todo list item written using useState logic.

 // Adds a new todo item to the list (useState version)  const handleAddItem = (e) => {    e.preventDefault();    const updatedTodos = [      ...todo,      { id: uuidv4(), title: item, isComplete: false },    ];    setTodo(updatedTodos);    setItem("");  };

In the above code, handleAddItem function is used for adding a new todo item to the list. We are creating a new array of updated todo list items and setting the state with updated todo list that also includes newly added item. We are updating the state directly from the function itself.

Lets look at the useReducer version of the same function.

// Adds a new todo item to the list (useReducer version)  const handleAddItem = (e) => {    e.preventDefault();    dispatch({ type: "ADD", payload: { item } });    setItem("");  };

The above function does the same thing i.e. it adds a new todo item to the list. In this scenario, the state management logic is separated and we are not directly updating the state from the function. Instead, here we are using dispatch function provided by useReducer to dispatch an action. Whenever an action is dispatched, the reducer function is called to modify and return the new state based on the type of action.

What is dispatch function?

A dispatch is a special function that dispatches an action object. It basically acts as a request to update the state. The dispatch function takes an action object as a parameter. You can pass anything to dispatch function but generally you should pass only the useful information to update the state.

Here is a sample action object.

const action = {    type: "ADD",    payload: {        todo: "Write a new blog article"    }}

The action object should generally contain following two properties:

  1. type - It basically tells reducer what type of action has happened. (eg. ADD, DELETE etc.)
  2. payload (optional) - An optional property with some additional information required to update the state.

The dispatch function invokes the reducer function to update and return the new state. The logic to update the state is done inside the reducer function instead of a React component. In this way, the state management can be separated from UI rendering logic.

What is a reducer function?

A reducer function handles all the logic of how a state should be modified. It takes two parameters:

  1. A current state
  2. An action

Following is a sample reducer function that handles the logic of updating todo list state.

export const ListReducerFn = (state, action) => {  switch (action.type) {    case "ADD": {      const updatedTodos = [        ...state,        { id: uuidv4(), title: action.payload.item, isComplete: false },      ];      return updatedTodos;    }    case "DELETE": {      const updatedTodos = state.filter(        (item) => item.id !== action.payload.id      );      return updatedTodos;    }    case "UPDATE": {      const updatedTodos = state.map((item) => {        if (item.id === action.payload.id) {          return { ...item, isComplete: !item.isComplete };        }        return item;      });      return updatedTodos;    }    default: {      throw new Error(`Unsupported action: ${action.type}`);    }  }};

Whenever an action is dispatched from a component, the reducer function is invoked to update and return the new state.

TL;DR

The useState and useReducer hooks allow you to add state management to the React component. The useReducer hook provides more flexibility as it allows you to separate UI rendering and state management logic.
You can check the code for above sample here.


Original Link: https://dev.to/vivekalhat/simplifying-state-management-with-usereducer-hook-50e4

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To