useHistory Hook
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)— recordnextas 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.canUndo—truewhen there is at least one past entry.canRedo—truewhen there is at least one future entry.
The App renders a counter:
- A
role="status"region (labelled "Current count") shows the current value, starting at0. - An Increment button calls
set(state + 1). - An Undo button calls
undo()and isdisabledwhenevercanUndois false. - A Redo button calls
redo()and isdisabledwhenevercanRedois 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)
- Model history as three pieces: past (array), present (value), and future (array). This makes every operation a small array shuffle.
- 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.
- 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.
- 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.
- 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
