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
- Setting Up a New React Project
- Syntax of
useReducer - Real-Time Project: Employee Management System
- Project Setup
- Creating the Employee Component
- Defining the Reducer Function
- Using
useReducerto Manage State - Adding and Deleting Employees
- Conclusion
Setting Up a New React Project
First, let’s create a new React project using the create-react-app tool.
- Open your terminal and run the following command:
npx create-react-app usereducer-employee-app - 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
reducerfunction specifies how the state should be updated based on actions. - The
useReducerhook initializes the state and provides thedispatchfunction. - The
dispatchfunction is used to send actions to the reducer to update the state.
Real-Time Project: Employee Management System
Project Setup
- Open the project directory in your code editor.
- Remove the default content from
src/App.jsandsrc/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
- Create a new file named
Employee.jsin thesrcdirectory. - Define the
Employeecomponent:// 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
- Reducer Function: The
reducerfunction 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
- Initialize State: We use the
useReducerhook 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); - Handle Input Change: The
handleInputChangefunction dispatches anupdateInputaction to update the input values in the state.const handleInputChange = (event) => { dispatch({ type: 'updateInput', field: event.target.name, value: event.target.value, }); }; - Handle Add Employee: The
handleAddEmployeefunction dispatches anaddEmployeeaction 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 }); } }; - Handle Delete Employee: The
handleDeleteEmployeefunction dispatches adeleteEmployeeaction 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
- Form Submission: The form submission is handled by the
handleAddEmployeefunction, which dispatches anaddEmployeeaction 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> - Displaying the List of Employees: The
employeesarray 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.