DeployU
Interviews / Frontend Engineering / This component's state logic is complex. Refactor it with `useReducer`.

Questions

This component's state logic is complex. Refactor it with `useReducer`.

refactoring State Management Interactive Quiz Code Examples

A developer has built a ShoppingCart component. The cart state includes a list of items (each with a productId, name, price, and quantity) and a totalPrice.

They are currently using multiple useState calls to manage these pieces of state. Adding, removing, or updating item quantities involves several setItems and setTotalPrice calls, making the logic spread out, hard to follow, and prone to inconsistencies.

You’ve been given the ShoppingCart component. Your task is to refactor its state management to use the useReducer hook.

  1. Centralize the cart’s state logic (adding, removing, updating quantity) into a single cartReducer function.
  2. Replace the multiple useState calls with a single useReducer call.
  3. Ensure the component’s functionality remains identical.
Your Code
import React, { useState, useEffect } from 'react';

// Helper to calculate total price
const calculateTotal = (items) => {
  return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
};

export default function ShoppingCart() {
  const [items, setItems] = useState([]);
  const [totalPrice, setTotalPrice] = useState(0);

  // Update total price whenever items change
  useEffect(() => {
    setTotalPrice(calculateTotal(items));
  }, [items]);

  const addItem = (product) => {
    setItems(prevItems => {
      const existingItem = prevItems.find(item => item.productId === product.id);
      if (existingItem) {
        return prevItems.map(item =>
          item.productId === product.id
            ? { ...item, quantity: item.quantity + 1 }
            : item
        );
      }
      return [...prevItems, { productId: product.id, name: product.name, price: product.price, quantity: 1 }];
    });
  };

  const removeItem = (productId) => {
    setItems(prevItems => prevItems.filter(item => item.productId !== productId));
  };

  const updateQuantity = (productId, newQuantity) => {
    setItems(prevItems =>
      prevItems.map(item =>
        item.productId === productId
          ? { ...item, quantity: newQuantity }
          : item
      )
    );
  };

  return (
    <div style={{ padding: '20px', border: '1px solid #ccc' }}>
      <h1>Shopping Cart</h1>
      <h2>Items</h2>
      {items.length === 0 ? (
        <p>Your cart is empty.</p>
      ) : (
        <ul>
          {items.map(item => (
            <li key={item.productId}>
              {item.name} (x{item.quantity}) - ${item.price * item.quantity}
              <button onClick={() => updateQuantity(item.productId, item.quantity + 1)}>+</button>
              <button onClick={() => updateQuantity(item.productId, item.quantity - 1)}>-</button>
              <button onClick={() => removeItem(item.productId)}>Remove</button>
            </li>
          ))}
        </ul>
      )}
      <h3>Total: ${totalPrice.toFixed(2)}</h3>
      <hr />
      <h2>Products</h2>
      <button onClick={() => addItem({ id: 1, name: 'Laptop', price: 1200 })}>Add Laptop</button>
      <button onClick={() => addItem({ id: 2, name: 'Mouse', price: 25 })}>Add Mouse</button>
    </div>
  );
}
Click "Run Code" to see output...
Click "Run Code" to see test results...
How Different Experience Levels Approach This
Junior Engineer
Surface Level

import React, { useState, useEffect } from 'react'; // Helper to calculate total price const calculateTotal = (items) => { return items.reduce((sum, item) => sum + item.price * item.quantity, 0); }; export default function ShoppingCart() { const [items, setItems] = useState([]); const [totalPrice, setTotalPrice] = useState(0); // Update total price whenever items change useEffect(() => { setTotalPrice(calculateTotal(items)); }, [items]); const addItem = (product) => { setItems(prevItems => { const existingItem = prevItems.find(item => item.productId === product.id); if (existingItem) { return prevItems.map(item => item.productId === product.id ? { ...item, quantity: item.quantity + 1 } : item ); } return [...prevItems, { productId: product.id, name: product.name, price: product.price, quantity: 1 }]; }); }; const removeItem = (productId) => { setItems(prevItems => prevItems.filter(item => item.productId !== productId)); }; const updateQuantity = (productId, newQuantity) => { setItems(prevItems => prevItems.map(item => item.productId === productId ? { ...item, quantity: newQuantity } : item ) ); }; return ( <div style={{ padding: '20px', border: '1px solid #ccc' }}> <h1>Shopping Cart</h1> <h2>Items</h2> {items.length === 0 ? ( <p>Your cart is empty.</p> ) : ( <ul> {items.map(item => ( <li key={item.productId}> {item.name} (x{item.quantity}) - ${item.price * item.quantity} <button onClick={() => updateQuantity(item.productId, item.quantity + 1)}>+</button> <button onClick={() => updateQuantity(item.productId, item.quantity - 1)}>-</button> <button onClick={() => removeItem(item.productId)}>Remove</button> </li> ))} </ul> )} <h3>Total: ${totalPrice.toFixed(2)}</h3> <hr /> <h2>Products</h2> <button onClick={() => addItem({ id: 1, name: 'Laptop', price: 1200 })}>Add Laptop</button> <button onClick={() => addItem({ id: 2, name: 'Mouse', price: 25 })}>Add Mouse</button> </div> ); }

Senior Engineer
Production Ready

import React, { useReducer, useEffect } from 'react'; // 1. Define the initial state for the cart const initialCartState = { items: [], totalPrice: 0, }; // Helper to calculate total price (can be reused in reducer) const calculateTotal = (items) => { return items.reduce((sum, item) => sum + item.price * item.quantity, 0); }; // 2. Define the reducer function function cartReducer(state, action) { switch (action.type) { case 'ADD_ITEM': { const product = action.payload; const existingItem = state.items.find(item => item.productId === product.id); let updatedItems; if (existingItem) { updatedItems = state.items.map(item => item.productId === product.id ? { ...item, quantity: item.quantity + 1 } : item ); } else { updatedItems = [...state.items, { productId: product.id, name: product.name, price: product.price, quantity: 1 }]; } return { ...state, items: updatedItems, totalPrice: calculateTotal(updatedItems), }; } case 'REMOVE_ITEM': { const productId = action.payload; const updatedItems = state.items.filter(item => item.productId !== productId); return { ...state, items: updatedItems, totalPrice: calculateTotal(updatedItems), }; } case 'UPDATE_QUANTITY': { const { productId, newQuantity } = action.payload; const updatedItems = state.items.map(item => item.productId === productId ? { ...item, quantity: newQuantity } : item ); return { ...state, items: updatedItems, totalPrice: calculateTotal(updatedItems), }; } default: throw new Error(`Unhandled action type: ${action.type}`); } } export default function ShoppingCart() { // 3. Replace useState with useReducer const [cartState, dispatch] = useReducer(cartReducer, initialCartState); const { items, totalPrice } = cartState; // Action dispatchers const addItem = (product) => { dispatch({ type: 'ADD_ITEM', payload: product }); }; const removeItem = (productId) => { dispatch({ type: 'REMOVE_ITEM', payload: productId }); }; const updateQuantity = (productId, newQuantity) => { // Prevent quantity from going below 1, or remove if 0 if (newQuantity <= 0) { dispatch({ type: 'REMOVE_ITEM', payload: productId }); } else { dispatch({ type: 'UPDATE_QUANTITY', payload: { productId, newQuantity } }); } }; return ( <div style={{ padding: '20px', border: '1px solid #ccc' }}> <h1>Shopping Cart</h1> <h2>Items</h2> {items.length === 0 ? ( <p>Your cart is empty.</p> ) : ( <ul> {items.map(item => ( <li key={item.productId}> {item.name} (x{item.quantity}) - ${item.price * item.quantity} <button onClick={() => updateQuantity(item.productId, item.quantity + 1)}>+</button> <button onClick={() => updateQuantity(item.productId, item.quantity - 1)}>-</button> <button onClick={() => removeItem(item.productId)}>Remove</button> </li> ))} </ul> )} <h3>Total: ${totalPrice.toFixed(2)}</h3> <hr /> <h2>Products</h2> <button onClick={() => addItem({ id: 1, name: 'Laptop', price: 1200 })}>Add Laptop</button> <button onClick={() => addItem({ id: 2, name: 'Mouse', price: 25 })}>Add Mouse</button> </div> ); }

What Makes the Difference?
  • Context over facts: Explains when and why, not just what
  • Real examples: Provides specific use cases from production experience
  • Trade-offs: Acknowledges pros, cons, and decision factors

Practice Question

When is useReducer generally preferred over useState?