Skip to content
UniCircle
MediumCodinguseReducer

Write a reducer for a to-do list

Question REACT-COD-001

In React, useReducer and Redux both update state with a pure function reducer(state, action) → newState. Write such a reducer for a to-do list, run it over a list of actions starting from an empty list, and print the final state.

Actions:

  • add <text> — append a new item with the next id (ids start at 1 and are never reused) and done = false.
  • toggle <id> — flip that item's done.
  • edit <id> <text> — replace that item's text.
  • remove <id> — delete that item.
  • clear-done — delete every item whose done is true.

Actions that mention an id not currently in the list do nothing.

Constraints

  • 1 ≤ number of actions ≤ 10^4
  • Texts are 1–100 printable characters and may contain spaces.

Input and output

Input
Line 1: the number of actions Next lines: one action each
Output
One line per remaining item, in list order: `<id> [x] <text>` if done or `<id> [ ] <text>` if not. Print `(empty)` if no items remain.

Examples

Input 1

6
add Buy notebooks
add Pay hostel fee
add Revise DBMS
toggle 2
edit 3 Revise DBMS unit 4
remove 1

Output

2 [x] Pay hostel fee
3 [ ] Revise DBMS unit 4

Ids are given in order of adding: 1, 2, 3. Item 2 is done; item 1 is removed.

Input 2

4
add A
toggle 1
clear-done
toggle 1

Output

(empty)

clear-done removes finished items; later actions on a removed id are ignored.

Show hints
  • Each case returns a new array: map to change one item, filter to remove.
  • Keep nextId in the state, not in a global variable.
Show the explanation

Model the state as { nextId, items } and handle each action type by returning a new state, never mutating the old one:

JavaScript
function todos(state, action) {
  switch (action.type) {
    case 'add':    return { nextId: state.nextId + 1, items: [...state.items, { id: state.nextId, text: action.text, done: false }] };
    case 'toggle': return { ...state, items: state.items.map(t => (t.id === action.id ? { ...t, done: !t.done } : t)) };
    case 'edit':   return { ...state, items: state.items.map(t => (t.id === action.id ? { ...t, text: action.text } : t)) };
    case 'remove': return { ...state, items: state.items.filter(t => t.id !== action.id) };
    case 'clear-done': return { ...state, items: state.items.filter(t => !t.done) };
    default: return state;
  }
}

Unknown ids fall out naturally: map changes nothing and filter removes nothing. Keeping nextId inside the state keeps the reducer pure (no outside counter), so replaying the same actions always gives the same result — the property Redux DevTools' time travel relies on. Immutable updates also let React detect changes with a cheap reference comparison.

Original UniCircle question. Last reviewed 22 Oct 2026.