JavaScript Curriculum
useReducer
mediumThe coffee shop cart has items, a total, a loading flag, and an error state — all changing together on every action. useState means four setters scattered across handlers. useReducer means one reducer with all the logic in one place.
useReducer
useReducer is an alternative to useState for complex state logic. Instead of calling multiple setters, you dispatch an action — the reducer function computes the entire new state from the previous state and the action.
Cart Reducer — Full CRUD
Dispatch ADD_ITEM, REMOVE_ITEM, UPDATE_QTY, CLEAR_CART:
const [state, dispatch] = useReducer(cartReducer, { items: [], total: 0 }) // ↑ current state ↑ dispatch fn ↑ reducer fn ↑ initial state
{
"items": [],
"total": 0
}// No actions dispatched yet// Reducer — pure function, same input → same output function cartReducer(state, action) { switch (action.type) { case 'ADD_ITEM': return { ...state, items: [...state.items, action.payload] } case 'CLEAR_CART': return { items: [], total: 0 } default: return state } }
Action Patterns — Three Styles
Flux standard, TypeScript discriminated unions, and Immer:
Most actions carry data in a payload property. The reducer uses payload to compute the next state.
// Action object: { type: 'ADD_ITEM', payload: { id: 1, name: 'Espresso', price: 2.50 } } { type: 'SET_FILTER', payload: { category: 'coffee' } } // Dispatch: dispatch({ type: 'ADD_ITEM', payload: item }) // Reducer case: case 'ADD_ITEM': return { ...state, items: [...state.items, { ...action.payload, qty: 1 }] }
useState vs useReducer — When to Switch
Side-by-side comparison and code:
{
"todos": [
{
"id": 1,
"text": "Grind coffee beans",
"done": true
},
{
"id": 2,
"text": "Steam the milk",
"done": false
},
{
"id": 3,
"text": "Clean the machine",
"done": false
}
],
"filter": "all",
"nextId": 4
}Your Challenge
Write a cartReducer function — pure, no side effects. Actions: ADD_ITEM (add or increment qty), REMOVE_ITEM (filter out), UPDATE_QTY (map to update, min 1), CLEAR_CART (reset). State: { items: [], total: 0 }. Test it: call the function directly with different states and actions, verify the output.
Challenge
Write a cartReducer with actions: ADD_ITEM, REMOVE_ITEM, UPDATE_QTY, CLEAR_CART. State: { items, total }. Each action must correctly update both items and total atomically. Add TypeScript discriminated union types for the actions.
useReducer
mediumThe coffee shop cart has items, a total, a loading flag, and an error state — all changing together on every action. useState means four setters scattered across handlers. useReducer means one reducer with all the logic in one place.
useReducer
useReducer is an alternative to useState for complex state logic. Instead of calling multiple setters, you dispatch an action — the reducer function computes the entire new state from the previous state and the action.
Cart Reducer — Full CRUD
Dispatch ADD_ITEM, REMOVE_ITEM, UPDATE_QTY, CLEAR_CART:
const [state, dispatch] = useReducer(cartReducer, { items: [], total: 0 }) // ↑ current state ↑ dispatch fn ↑ reducer fn ↑ initial state
{
"items": [],
"total": 0
}// No actions dispatched yet// Reducer — pure function, same input → same output function cartReducer(state, action) { switch (action.type) { case 'ADD_ITEM': return { ...state, items: [...state.items, action.payload] } case 'CLEAR_CART': return { items: [], total: 0 } default: return state } }
Action Patterns — Three Styles
Flux standard, TypeScript discriminated unions, and Immer:
Most actions carry data in a payload property. The reducer uses payload to compute the next state.
// Action object: { type: 'ADD_ITEM', payload: { id: 1, name: 'Espresso', price: 2.50 } } { type: 'SET_FILTER', payload: { category: 'coffee' } } // Dispatch: dispatch({ type: 'ADD_ITEM', payload: item }) // Reducer case: case 'ADD_ITEM': return { ...state, items: [...state.items, { ...action.payload, qty: 1 }] }
useState vs useReducer — When to Switch
Side-by-side comparison and code:
{
"todos": [
{
"id": 1,
"text": "Grind coffee beans",
"done": true
},
{
"id": 2,
"text": "Steam the milk",
"done": false
},
{
"id": 3,
"text": "Clean the machine",
"done": false
}
],
"filter": "all",
"nextId": 4
}Your Challenge
Write a cartReducer function — pure, no side effects. Actions: ADD_ITEM (add or increment qty), REMOVE_ITEM (filter out), UPDATE_QTY (map to update, min 1), CLEAR_CART (reset). State: { items: [], total: 0 }. Test it: call the function directly with different states and actions, verify the output.
Challenge
Write a cartReducer with actions: ADD_ITEM, REMOVE_ITEM, UPDATE_QTY, CLEAR_CART. State: { items, total }. Each action must correctly update both items and total atomically. Add TypeScript discriminated union types for the actions.