useRefHistory()
Live demo: useRefHistory
Every change becomes a history snapshot: undo, redo, clear, and cap length with capacity.
Loading demo...
Demo sourceJSX
Overview
useRefHistory keeps a linear snapshot history of a value and moves a pointer through it for undo/redo. Each set appends a new snapshot after the current pointer (discarding any “future” branch), while capacity trims the oldest entries so memory stays bounded. undo/redo only move the pointer (they do not mutate past snapshots), and clear collapses history down to the current snapshot so you can checkpoint after a save or reset exploration.
What it accepts
initialValue: T.options: UseRefHistoryOptions = {}.
What it returns
value: The snapshot athistory[pointer]. TypeT.set: Appends a new snapshot after the current pointer and trims bycapacity. Type(next: T) => void.history: Ordered list of snapshots (length bounded bycapacity). TypeT[].pointer: Index intohistoryfor the active snapshot. Typenumber.canUndo:truewhenpointercan move backward. Typeboolean.canRedo:truewhenpointercan move forward. Typeboolean.undo: Movespointerback one step. Type() => void.redo: Movespointerforward one step. Type() => void.clear: Keeps only the current snapshot and resetspointerto0. Type() => void.
Usage
Real-world example: a simple counter with bounded history (capacity) and undo/redo/clear controls.
import useRefHistory from '@dedalik/use-react/useRefHistory'
function Example() {
const { value, set, history, pointer, canUndo, canRedo, undo, redo, clear } = useRefHistory(0, {
capacity: 5,
})
return (
<div>
<h3>Counter with history</h3>
<p>Current value: {value}</p>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
<button type='button' onClick={() => set(value + 1)}>
+1
</button>
<button type='button' onClick={() => set(value - 1)}>
-1
</button>
<button type='button' onClick={undo} disabled={!canUndo}>
Undo
</button>
<button type='button' onClick={redo} disabled={!canRedo}>
Redo
</button>
<button type='button' onClick={clear}>
Clear history
</button>
</div>
<p>
Pointer: {pointer} / {history.length - 1} (snapshots kept: {history.length}, max 5)
</p>
<p>Trail: {history.join(' → ')}</p>
</div>
)
}
export default function Demo() {
return <Example />
}API Reference
useRefHistory
Signature: useRefHistory(initialValue: T, options: UseRefHistoryOptions = {}): UseRefHistoryReturn<T>
Parameters
initialValue(T) - Starting snapshot; becomes the first entry inhistory.options(optionalUseRefHistoryOptions) -capacitycaps how many snapshots are kept (oldest dropped). Default:{}.
Returns
Object with:
value- The snapshot athistory[pointer]. (T).set- Appends a new snapshot after the current pointer and trims bycapacity. ((next: T) => void).history- Ordered list of snapshots (length bounded bycapacity). (T[]).pointer- Index intohistoryfor the active snapshot. (number).canUndo-truewhenpointercan move backward. (boolean).canRedo-truewhenpointercan move forward. (boolean).undo- Movespointerback one step. (() => void).redo- Movespointerforward one step. (() => void).clear- Keeps only the current snapshot and resetspointerto0. (() => void).
Copy-paste hook
import { useCallback, useState } from 'react'
export interface UseRefHistoryOptions {
capacity?: number
}
export interface UseRefHistoryReturn<T> {
value: T
set: (next: T) => void
history: T[]
pointer: number
canUndo: boolean
canRedo: boolean
undo: () => void
redo: () => void
clear: () => void
}
/**
* State with snapshot history and undo/redo controls.
*/
export default function useRefHistory<T>(initialValue: T, options: UseRefHistoryOptions = {}): UseRefHistoryReturn<T> {
const { capacity = 10 } = options
const [state, setState] = useState(() => ({ history: [initialValue] as T[], pointer: 0 }))
const { history, pointer } = state
const value = history[pointer] ?? history[history.length - 1]
const set = useCallback(
(next: T) => {
setState((prev) => {
const base = prev.history.slice(0, prev.pointer + 1)
const nextHistory = [...base, next]
const max = Math.max(1, capacity)
const trimmed = nextHistory.length > max ? nextHistory.slice(nextHistory.length - max) : nextHistory
return { history: trimmed, pointer: trimmed.length - 1 }
})
},
[capacity],
)
const undo = useCallback(() => {
setState((prev) => ({ ...prev, pointer: Math.max(0, prev.pointer - 1) }))
}, [])
const redo = useCallback(() => {
setState((prev) => ({ ...prev, pointer: Math.min(prev.history.length - 1, prev.pointer + 1) }))
}, [])
const clear = useCallback(() => {
setState((prev) => ({ history: [prev.history[prev.pointer]], pointer: 0 }))
}, [])
return {
value,
set,
history,
pointer,
canUndo: pointer > 0,
canRedo: pointer < history.length - 1,
undo,
redo,
clear,
}
}import { useCallback, useState } from 'react'
export default function useRefHistory(initialValue, options = {}) {
const { capacity = 10 } = options
const [state, setState] = useState(() => ({ history: [initialValue], pointer: 0 }))
const { history, pointer } = state
const value = history[pointer] ?? history[history.length - 1]
const set = useCallback(
(next) => {
setState((prev) => {
const base = prev.history.slice(0, prev.pointer + 1)
const nextHistory = [...base, next]
const max = Math.max(1, capacity)
const trimmed = nextHistory.length > max ? nextHistory.slice(nextHistory.length - max) : nextHistory
return { history: trimmed, pointer: trimmed.length - 1 }
})
},
[capacity],
)
const undo = useCallback(() => {
setState((prev) => ({ ...prev, pointer: Math.max(0, prev.pointer - 1) }))
}, [])
const redo = useCallback(() => {
setState((prev) => ({ ...prev, pointer: Math.min(prev.history.length - 1, prev.pointer + 1) }))
}, [])
const clear = useCallback(() => {
setState((prev) => ({ history: [prev.history[prev.pointer]], pointer: 0 }))
}, [])
return {
value,
set,
history,
pointer,
canUndo: pointer > 0,
canRedo: pointer < history.length - 1,
undo,
redo,
clear,
}
}