# 6760 MINUndo / Redo
# 67
60 MIN
Overview
Build a notes editor whose add, edit, toggle, and delete actions are tracked in a useReducer past/present/future history, so every change can be undone and redone with buttons or keyboard shortcuts.
Requirements
- The list starts with the notes "Buy groceries" and "Walk the dog".
- Each note is editable through an input (
data-testid="note-input-<id>"). - Each note has a done toggle (
data-testid="toggle-note-<id>") and a delete button (data-testid="delete-note-<id>"). - Undo (
data-testid="undo-button") and redo (data-testid="redo-button") are disabled on first render. - Adding a note (
data-testid="add-note") appends it to the list and clears the new-note field (data-testid="note-input"). - A change enables undo; undoing restores the previous list, disables undo when there is nothing left to undo, and enables redo.
- Redo reapplies an undone change and disables itself once there is nothing left to redo.
- Adding, editing, toggling, and deleting a note can each be undone.
- An undone change can be redone.
- Consecutive edits to the same note collapse into a single undo step.
- An edit that does not change the text is ignored and adds no history.
- Adding an empty or whitespace-only note is ignored and adds no history.
- Undo is triggered by Cmd/Ctrl+Z.
- Redo is triggered by Shift+Ctrl+Z.
- Pressing undo with no history or redo with nothing to redo leaves the list unchanged.
- The status line (
data-testid="history-status") reports how many undo and redo steps are available. - New notes always get unique identifiers, even after undoing and adding again.
- Making a new change after undoing clears the redo stack.
Notes
Shortcuts.tsxis read-only.App.tsxwires the UI and the keyboard shortcuts;reducer.tsowns the types, the initial state, and all history logic.- The history shape most people reach for is
{ past, present, future }:presentis what renders,pastandfutureare stacks of snapshots. - Return the same state object for a no-op (blank note, edit that doesn't change the text, undo/redo with an empty stack) so React bails out and history stays clean.
- The keyboard shortcuts fire even when the buttons are disabled, so the reducer — not the UI — must guard the empty stacks.
- Keep the id counter outside the history: rewinding it on undo would let two different notes share an id.
- Collapsing a burst of edits to the same note into a single undo step needs a marker of which note was edited last, reset by every other action.
- APIs:
useReducer,useCallback,useEffect,KeyboardEvent.
Tests
- renders the initial notes
- disables undo and redo on load
- adds a note and clears the input
- enables undo after a change and disables it again after undoing
- redoes an undone change
- undoes a deletion and restores the note
- undoes a toggle and restores the previous state
- undoes an edit and restores the previous text
- collapses continuous edits to one note into a single undo step
- ignores an edit that does not change the text
- does not push history when adding an empty note
- does not push history when adding a whitespace-only note
- undoes with Ctrl+Z
- undoes with Cmd+Z
- redoes with Shift+Ctrl+Z
- does nothing when undo is pressed with no history
- does nothing when redo is pressed with no future
- reports the available undo and redo steps
- keeps note identifiers unique after undo and redo
- clears the redo stack when a new change is made