Lesson 5 of 7 · 12 min · 1 hands-on step

State, Events & Reducers

Manage changing data predictably, the way React's useReducer and Redux do.

State is data that changes

The items in a cart, whether a menu is open, what's typed in a search box: all state. Bugs love state, because many parts of the app can change it.

Reducers: one place for every change

A reducer is a pure function: (currentState, action) → newState. Every change in your app becomes an action like { type: "add", text: "Milk" }.

JavaScript · try it
function counter(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "reset":
      return { count: 0 };
    default:
      return state;
  }
}

let state = { count: 0 };
for (const action of [{ type: "increment" }, { type: "increment" }, { type: "reset" }, { type: "increment" }]) {
  state = counter(state, action);
  console.log(action.type, "→", state);
}

Because it's pure (same input, same output, no side effects), it's trivially easy to test, and you can replay every action to reproduce a bug.

Immutability: copy, don't change

JavaScript · try it
const before = { name: "Ada", skills: ["JS"] };
const after = { ...before, skills: [...before.skills, "SQL"] };
console.log(before.skills, after.skills);
console.log(before === after); // false: React sees "something changed"

React decides what to re-render by checking whether state is a new object. Change an array in place and React may not notice. That's one of the most common React bugs.

In React

const [todos, dispatch] = useReducer(todosReducer, []);
<button onClick={() => dispatch({ type: "add", text })}>Add</button>

Your reducer from the practice step would work in a real React app unchanged.

🧪 Practice

A reducer takes the current state and an action, and returns the new state (never modifying the old one).

Write todos(state, action) handling:

  • { type: "add", text } → append { id, text, done: false } where id = state.length + 1
  • { type: "toggle", id } → flip done for that todo
  • { type: "remove", id } → remove it
  • anything else → return state unchanged
JavaScript
function todos(state, action) {
  return state;
}

Output appears here. Press Run or Ctrl/⌘ + Enter.

✅ Check your understanding

  1. Why must reducers return new objects instead of changing the old ones?

Create a free account to track your progress.