Reducers
When many handlers update related state, a reducer centralises the update rules. It receives the current state and an action, then returns the next state without mutation.
function tasksReducer(tasks, action) { switch (action.type) { case "added": return [ ...tasks, { id: action.id, title: action.title, complete: false } ]; case "deleted": return tasks.filter( (task) => task.id !== action.id ); default: throw new Error(`Unknown action: ${action.type}`); } }
Use it with const [tasks, dispatch] = useReducer(tasksReducer, initialTasks). Actions should describe what happened; the reducer decides how state changes.
Context
Context makes a value available to distant descendants without passing it through every intermediate component. It suits broadly needed values such as a theme, current account or shared dispatch function. Prefer ordinary props when the relationship is local and explicit.
Combining them
A provider component can own a reducer and expose state and dispatch through context. Wrap access in focused Hooks such as useTasks and useTaskDispatch so components do not depend on context implementation details.
Follow an action
- A component dispatches an object describing an event, for example
{ type: "deleted", id }. - React calls the reducer with the current tasks and that action.
- The reducer returns a new array without performing side effects.
- React stores the result and renders consumers again.
The reducer should be a pure calculation. Put requests, storage and notifications in event handlers, Effects or dedicated data layers rather than inside the reducer.
Challenge: finish the task reducer
Add toggled and renamed cases, then replace the task application's separate update handlers with dispatches. Throw for unknown action types so spelling mistakes fail clearly during development.
Create separate contexts for task data and dispatch. A component that only dispatches actions then does not need the data value from the same context.
