React useReducer Hook

Introduction

In this chapter, we will explore the useReducer hook in React. This hook is an alternative to useState for managing complex state logic in functional components. It provides a way to handle state transitions in a predictable manner using a reducer function. We will create a new React project from scratch, explain the syntax of useReducer, and walk through a real-time project step by step to demonstrate its usage.

Table of Contents

  1. Setting Up a New React Project
  2. Syntax of useReducer
  3. Real-Time Project: Employee Management System
    • Project Setup
    • Creating the Employee Component
    • Defining the Reducer Function
    • Using useReducer to Manage State
    • Adding and Deleting Employees
  4. Conclusion

Setting Up a New React Project

First, let’s create a new React project using the create-react-app tool.

  1. Open your terminal and run the following command:
    npx create-react-app usereducer-employee-app
    
  2. Navigate to the project directory:
    cd usereducer-employee-app
    

Syntax of useReducer

The useReducer hook takes a reducer function and an initial state as arguments and returns the current state and a dispatch function.

Syntax

const [state, dispatch] = useReducer(reducer, initialState);
  • reducer: A function that specifies how the state should be updated based on actions.
  • initialState: The initial state value.
  • state: The current state.
  • dispatch: A function to send actions to the reducer.

Example

import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
    </div>
  );
}

export default Counter;

Explanation:

  • The reducer function specifies how the state should be updated based on actions.
  • The useReducer hook initializes the state and provides the dispatch function.
  • The dispatch function is used to send actions to the reducer to update the state.

Real-Time Project: Employee Management System

Project Setup

  1. Open the project directory in your code editor.
  2. Remove the default content from src/App.js and src/App.css:
    // src/App.js
    import React from 'react';
    import './App.css';
    import Employee from './Employee';
    
    function App() {
      return (
        <div className="App">
          <h1>Employee Management System</h1>
          <Employee />
        </div>
      );
    }
    
    export default App;
    
    /* src/App.css */
    .App {
      text-align: center;
      padding: 20px;
    }
    
    h1 {
      margin-bottom: 20px;
    }
    

Creating the Employee Component

  1. Create a new file named Employee.js in the src directory.
  2. Define the Employee component:
    // src/Employee.js
    import React, { useReducer } from 'react';
    import './Employee.css';
    
    const initialState = {
      employees: [],
      inputValue: { id: '', firstName: '', lastName: '', email: '' },
    };
    
    function reducer(state, action) {
      switch (action.type) {
        case 'addEmployee':
          return {
            ...state,
            employees: [...state.employees, action.payload],
            inputValue: { id: '', firstName: '', lastName: '', email: '' },
          };
        case 'deleteEmployee':
          return {
            ...state,
            employees: state.employees.filter((_, index) => index !== action.index),
          };
        case 'updateInput':
          return {
            ...state,
            inputValue: { ...state.inputValue, [action.field]: action.value },
          };
        default:
          return state;
      }
    }
    
    function Employee() {
      const [state, dispatch] = useReducer(reducer, initialState);
    
      const handleAddEmployee = (event) => {
        event.preventDefault();
        if (state.inputValue.id && state.inputValue.firstName && state.inputValue.lastName && state.inputValue.email) {
          dispatch({ type: 'addEmployee', payload: state.inputValue });
        }
      };
    
      const handleInputChange = (event) => {
        dispatch({
          type: 'updateInput',
          field: event.target.name,
          value: event.target.value,
        });
      };
    
      return (
        <div className="container mt-5">
          <form onSubmit={handleAddEmployee}>
            <div className="form-group">
              <label htmlFor="id">ID</label>
              <input
                type="text"
                id="id"
                name="id"
                className="form-control"
                value={state.inputValue.id}
                onChange={handleInputChange}
              />
            </div>
            <div className="form-group">
              <label htmlFor="firstName">First Name</label>
              <input
                type="text"
                id="firstName"
                name="firstName"
                className="form-control"
                value={state.inputValue.firstName}
                onChange={handleInputChange}
              />
            </div>
            <div className="form-group">
              <label htmlFor="lastName">Last Name</label>
              <input
                type="text"
                id="lastName"
                name="lastName"
                className="form-control"
                value={state.inputValue.lastName}
                onChange={handleInputChange}
              />
            </div>
            <div className="form-group">
              <label htmlFor="email">Email</label>
              <input
                type="email"
                id="email"
                name="email"
                className="form-control"
                value={state.inputValue.email}
                onChange={handleInputChange}
              />
            </div>
            <button type="submit" className="btn btn-primary">Add Employee</button>
          </form>
          <ul className="list-group mt-3">
            {state.employees.map((employee, index) => (
              <li key={index} className="list-group-item d-flex justify-content-between align-items-center">
                {employee.id} - {employee.firstName} {employee.lastName} ({employee.email})
                <button className="btn btn-danger btn-sm" onClick={() => dispatch({ type: 'deleteEmployee', index })}>
                  Delete
                </button>
              </li>
            ))}
          </ul>
        </div>
      );
    }
    
    export default Employee;
    

Defining the Reducer Function

  1. Reducer Function: The reducer function specifies how the state should be updated based on actions.
    function reducer(state, action) {
      switch (action.type) {
        case 'addEmployee':
          return {
            ...state,
            employees: [...state.employees, action.payload],
            inputValue: { id: '', firstName: '', lastName: '', email: '' },
          };
        case 'deleteEmployee':
          return {
            ...state,
            employees: state.employees.filter((_, index) => index !== action.index),
          };
        case 'updateInput':
          return {
            ...state,
            inputValue: { ...state.inputValue, [action.field]: action.value },
          };
        default:
          return state;
      }
    }
    

Using useReducer to Manage State

  1. Initialize State: We use the useReducer hook to manage the state, which includes the list of employees and the input values.
    const initialState = {
      employees: [],
      inputValue: { id: '', firstName: '', lastName: '', email: '' },
    };
    
    const [state, dispatch] = useReducer(reducer, initialState);
    
  2. Handle Input Change: The handleInputChange function dispatches an updateInput action to update the input values in the state.
    const handleInputChange = (event) => {
      dispatch({
        type: 'updateInput',
        field: event.target.name,
        value: event.target.value,
      });
    };
    
  3. Handle Add Employee: The handleAddEmployee function dispatches an addEmployee action to add a new employee to the list.
    const handleAddEmployee = (event) => {
      event.preventDefault();
      if (state.inputValue.id && state.inputValue.firstName && state.inputValue.lastName && state.inputValue.email) {
        dispatch({ type: 'addEmployee', payload: state.inputValue });
      }
    };
    
  4. Handle Delete Employee: The handleDeleteEmployee function dispatches a deleteEmployee action to remove an employee from the list.
    <button className="btn btn-danger btn-sm" onClick={() => dispatch({ type: 'deleteEmployee', index })}>
      Delete
    </button>
    

Adding and Deleting Employees

  1. Form Submission: The form submission is handled by the handleAddEmployee function, which dispatches an addEmployee action to add a new employee to the list.
    <form onSubmit={handleAddEmployee}>
      <div className="form-group">
        <label htmlFor="id">ID</label>
        <input
          type="text"
          id="id"
          name="id"
          className="form-control"
          value={state.inputValue.id}
          onChange={handleInputChange}
        />
      </div>
      <div className="form-group">
        <label htmlFor="firstName">First Name</label>
        <input
          type="text"
          id="firstName"
          name="firstName"
          className="form-control"
          value={state.inputValue.firstName}
          onChange={handleInputChange}
        />
      </div>
      <div className="form-group">
        <label htmlFor="lastName">Last Name</label>
        <input
          type="text"
          id="lastName"
          name="lastName"
          className="form-control"
          value={state.inputValue.lastName}
          onChange={handleInputChange}
        />
      </div>
      <div className="form-group">
        <label htmlFor="email">Email</label>
        <input
          type="email"
          id="email"
          name="email"
          className="form-control"
          value={state.inputValue.email}
          onChange={handleInputChange}
        />
      </div>
      <button type="submit" className="btn btn-primary">Add Employee</button>
    </form>
    
  2. Displaying the List of Employees: The employees array is mapped to display each employee in an unordered list (<ul>).
    <ul className="list-group mt-3">
      {state.employees.map((employee, index) => (
        <li key={index} className="list-group-item d-flex justify-content-between align-items-center">
          {employee.id} - {employee.firstName} {employee.lastName} ({employee.email})
          <button className="btn btn-danger btn-sm" onClick={() => dispatch({ type: 'deleteEmployee', index })}>
            Delete
          </button>
        </li>
      ))}
    </ul>
    

Conclusion

In this chapter, we created a new React project and explored the useReducer hook. We learned the syntax of useReducer and walked through a real-time project step by step to create an Employee Management System application. By understanding and using the useReducer hook, you can manage complex state logic in your functional components more effectively, making your React applications more dynamic and interactive.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top