useReducer is a tool in React that helps manage the state in a more organized way. It's like a central hub where you can handle different changes to your state based on various actions. One example use case for 'useReducer' in React is managing a shopping cart state. You can use 'useReducer' to handle actions like adding items to the cart, removing items, and updating quantities. This helps simplify complex state logic and keeps related code organized in one place, improving the maintainability and scalability of your application.
Prerequisites
Steps to Create the React Application
Step 1: Create a React application using the following command:
npx create-react-app foldername
Step 2: After creating your project folder i.e. foldername, move to it using the following command:
cd foldername
Step 3: After setting up react environment on your system, we can start by creating an App.js file and create a file by the name of components in which we will write our desired function.
Project Structure
The updated dependencies in package.json file will look like:
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
Approach
- First define the Reducer Function this is basically a regular JavaScript function that takes two parameters
- state: This represents the current state of our application.
- action: This is an object that describes the type of action we want to perform and optionally carries additional data with it (payload).
- Inside the reducer function, we typically use a switch statement to handle different types of actions that can occur in our application. These actions are represented by strings (action types) and describes how the state should be updated.
- After handling all possible action types, we return the new state object. It's crucial to always return a new state object and never modify the existing state directly.
- Finally, we include a default case in our switch statement to handle any action types that are not explicitly defined. In most cases, this default case simply returns the current state without making any changes.
Example 1: Let's consider a simple shopping cart application. The state of the shopping cart might include an array of items, each with properties like name, quantity, and price. Users can add items to the cart, remove items, or update the quantity of items.
/* index.css */
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
button {
background-color: #f9f9f9;
}
}
//App.js
import React, { useReducer } from 'react'
import './styles.css'
// Define reducer function
const cartReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM':
return {
...state,
items: [...state.items, action.payload]
}
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(
(item) => item.id !== action.payload.id
)
}
case 'CLEAR_CART':
return {
...state,
items: []
}
default:
return state
}
}
const App = () => {
// Initial state for the shopping cart
const initialState = {
items: []
}
// useReducer hook to manage state
const [cartState, dispatch] = useReducer(cartReducer, initialState)
// Function to add item to cart
const addItemToCart = (item) => {
dispatch({ type: 'ADD_ITEM', payload: item })
}
// Function to remove item from cart
const removeItemFromCart = (item) => {
dispatch({ type: 'REMOVE_ITEM', payload: item })
}
// Function to clear the cart
const clearCart = () => {
dispatch({ type: 'CLEAR_CART' })
}
return (
<div className="shopping-cart">
<h2>Shopping Cart</h2>
<ul className="product-list">
{cartState.items.map((item) => (
<li key={item.id} className="product-item">
{item.name} - Rs {item.price}
<button className='removeBtn'
onClick={() => removeItemFromCart(item)}>
Remove
</button>
</li>
))}
</ul>
<button onClick={clearCart}>Clear Cart</button>
<hr />
{/* Product list */}
<h2>Available Products</h2>
<ul className="product-list">
<li className="product-item">
<button
onClick={() =>
addItemToCart({
id: 1,
name: 'Product 1',
price: 10
})
}
>
Add Product 1 to Cart
</button>
</li>
<li className="product-item">
<button
onClick={() =>
addItemToCart({
id: 2,
name: 'Product 2',
price: 20
})
}
>
Add Product 2 to Cart
</button>
</li>
<li className="product-item">
<button
onClick={() =>
addItemToCart({
id: 3,
name: 'Product 3',
price: 30
})
}
>
Add Product 3 to Cart
</button>
</li>
</ul>
</div>
)
}
export default App
Output:
Example 2: Let's consider another example i.e. a task management application where users can create, edit, and delete tasks. Each task has properties like title, description, and status (e.g., "pending" and "completed").
/* App.css */
.container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
h1 {
text-align: center;
}
.task-list {
list-style: none;
padding: 0;
}
.task-item {
background-color: #f9f9f9;
border: 1px solid #ddd;
margin-bottom: 10px;
padding: 15px;
}
.task-item h2 {
margin-top: 0;
}
.task-item p {
margin: 10px 0;
}
.button-container {
display: flex;
justify-content: flex-end;
}
.complete-button,
.delete-button,
.add-button {
padding: 8px 16px;
margin-left: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.complete-button {
background-color: #5cb85c;
color: #fff;
}
.delete-button {
background-color: #d9534f;
color: #fff;
}
.add-button {
background-color: #337ab7;
color: #fff;
}
.complete-button:hover,
.delete-button:hover,
.add-button:hover {
background-color: #4cae4c;
}
.add-button:hover {
background-color: #286090;
}
// App.js
import React, { useReducer } from 'react';
import './App.css';
// Reducer function
const taskReducer = (state, action) => {
switch (action.type) {
case 'ADD_TASK':
return {
...state,
tasks: [...state.tasks, action.payload]
};
case 'UPDATE_TASK':
return {
...state,
tasks: state.tasks.map(task =>
task.id === action.payload.id ?
{ ...task, ...action.payload.updates } : task
)
};
case 'DELETE_TASK':
return {
...state,
tasks: state.tasks.filter(
task => task.id !== action.payload)
};
default:
return state;
}
};
const App = () => {
// Initial state
const initialState = {
tasks: []
};
// useReducer hook
const [state, dispatch] =
useReducer(taskReducer, initialState);
const addTask = task => {
dispatch({ type: 'ADD_TASK', payload: task });
};
const updateTask = (taskId, updates) => {
dispatch({ type: 'UPDATE_TASK', payload: { id: taskId, updates } });
};
const deleteTask = taskId => {
dispatch({ type: 'DELETE_TASK', payload: taskId });
};
return (
<div className="container">
<h1>Task Manager</h1>
<ul className="task-list">
{state.tasks.map((task, index) => (
<li key={task.id} className="task-item">
<div>
<h2>{`${index + 1}. ${task.title}`}</h2>
<p>{task.description}</p>
<p>Status: {task.status}</p>
<div className="button-container">
<button
onClick={
() => updateTask(task.id, { status: 'completed' })}
className="complete-button">
Complete
</button>
<button onClick={
() => deleteTask(task.id)} className="delete-button">
Delete
</button>
</div>
</div>
</li>
))}
</ul>
<button
onClick={() =>
addTask({
id: Date.now(),
title: `Task ${state.tasks.length + 1}`,
description: '',
status: 'pending'
})
}
className="add-button">
Add Task
</button>
</div>
);
};
export default App;
Output:
Steps to run the Application:
Type the following command in the terminal:
npm start
Type the following URL in the browser if the browser doesn't opens automatically:
http://localhost:3000/
UseCases for useReducer
- Shopping Cart: Manage adding, removing, and updating items in a shopping cart.
- Authentication: Handle login/logout actions and user info updates.
- Forms: Manage form state, input changes, submission, and validation.
- Wizard Steps: Control multi-step processes or wizards in an application.
- Real-Time Updates: Handle state changes in response to real-time data updates.