useHistory Hook

HardReact Hooks25 min6 tests

Build a custom useHistory hook that gives any value a full undo/redo timeline, then wire it to a counter UI with disabled-aware Undo and Redo buttons.

Many editors, drawing apps, and form builders need an "undo/redo" timeline for their state. In this challenge you implement a reusable useHistory(initial) custom hook inside App.tsx and use it to drive a small counter.

useHistory(initial) must return an object with exactly these members:

  • state — the current value (the "present").
  • set(next) — record next as the new present. This pushes the old present onto the undo stack AND discards the entire redo stack (you can't redo into a future you've branched away from).
  • undo() — move one step back in time. No-op when there is nothing to undo.
  • redo() — move one step forward in time. No-op when there is nothing to redo.
  • canUndotrue when there is at least one past entry.
  • canRedotrue when there is at least one future entry.

The App renders a counter:

  • A role="status" region (labelled "Current count") shows the current value, starting at 0.
  • An Increment button calls set(state + 1).
  • An Undo button calls undo() and is disabled whenever canUndo is false.
  • A Redo button calls redo() and is disabled whenever canRedo is false.

The subtle, must-handle case: if you undo a few steps and then perform a fresh set (e.g. Increment), the previously-undone redo history must be thrown away — undoing afterward returns to the value just before the new set, never replaying the discarded branch.

Requirements

  • Implement useHistory(initial) inside App.tsx returning { state, set, undo, redo, canUndo, canRedo }.
  • state starts equal to initial; the role="status" region displays it.
  • set(next) makes next the present, records the previous present for undo, and clears the redo stack.
  • undo() moves one step back and is a no-op when there is no past; redo() moves one step forward and is a no-op when there is no future.
  • canUndo / canRedo accurately reflect whether undo / redo are currently possible, and drive the disabled state of the Undo / Redo buttons.
  • The Increment button calls set(state + 1); buttons are real <button> elements and the status value lives in a role="status" region.
  • A fresh set after one or more undos discards the redo stack.
Hints (5)
  1. Model history as three pieces: past (array), present (value), and future (array). This makes every operation a small array shuffle.
  2. On set: past becomes [...past, present], present becomes the new value, and future is reset to [] — that single reset is what discards the redo stack.
  3. On undo: pop the last item from past into present, and unshift the old present onto future. On redo: do the mirror image. Bail out early when the relevant array is empty.
  4. canUndo is past.length > 0 and canRedo is future.length > 0 — derive them on every render rather than storing them, so they never get out of sync.
  5. useReducer is a clean fit here: one reducer handling SET / UNDO / REDO keeps all the transitions in one place and avoids stale-closure bugs.

Topics

  • React
  • React Hooks

Asked at

Hotstar · Ola · Flipkart · Rapido · Dream11 · Swiggy

Join Us
blur