useStateWithHistory
Tracks a given state giving you an entire history of its changes. Also includes methods to navigate through the history.
Installation
npx rabbithook@latest add use-state-with-history
Usage
import { useStateWithHistory from "@/hooks/use-state-with-history";
function Component() { const [ count, setCount, { history, pointer, back, forward, go, } ] = useStateWithHistory(0, { capacity: 20 });
return ( <> <p>Count: { count }</p> <p>History: { JSON.stringify(history) }</p> <p>CurrentPosition: { JSON.stringify(pointer) }</p>
<button onClick={back}>Back</button> <button onClick={forward}>Forward</button> <button onClick={() => go(5)}>Go</button> </> );}
Code
import { useState, useRef, useCallback } from "react";
type IUseStateWithHistory<T> = [ T, (v: T | ((currentValue: T) => T)) => void, { history: T[]; pointer: number; back: () => void; forward: () => void; go: (index: number) => void; }];
function useStateWithHistory<T>(defaultValue: T, { capacity = 10 } = {}):IUseStateWithHistory<T> { const [value, setValue] = useState(defaultValue); const historyRef = useRef([value]); const pointerRef = useRef(0);
const set = useCallback((v: T | ((currentValue: T) => T)) => { const resolvedValue = v instanceof Function ? v(value) : v;
if (historyRef.current[pointerRef.current] !== resolvedValue) { if (pointerRef.current < historyRef.current.length - 1) { historyRef.current.splice(pointerRef.current + 1); }
historyRef.current.push(resolvedValue);
while (historyRef.current.length > capacity) { historyRef.current.shift(); }
pointerRef.current = historyRef.current.length - 1; }
setValue(resolvedValue); }, [capacity, value])
const back = useCallback(() => { if (pointerRef.current <= 0) return;
pointerRef.current--;
setValue(historyRef.current[pointerRef.current]); }, [])
const forward = useCallback(() => { if (pointerRef.current >= historyRef.current.length - 1) return;
pointerRef.current++;
setValue(historyRef.current[pointerRef.current]); }, [])
const go = useCallback((index: number) => { if (index < 0 || index >= historyRef.current.length - 1) return;
pointerRef.current = index;
setValue(historyRef.current[pointerRef.current]); }, [])
return [ value, set, { history: historyRef.current, pointer: pointerRef.current, back, forward, go } ];}
export default useStateWithHistory;
import { useState, useRef, useCallback } from "react";
function useStateWithHistory(defaultValue, { capacity = 10 } = {}) { const [value, setValue] = useState(defaultValue); const historyRef = useRef([value]); const pointerRef = useRef(0);
const set = useCallback((v) => { const resolvedValue = v instanceof Function ? v(value) : v;
if (historyRef.current[pointerRef.current] !== resolvedValue) { if (pointerRef.current < historyRef.current.length - 1) { historyRef.current.splice(pointerRef.current + 1); }
historyRef.current.push(resolvedValue);
while (historyRef.current.length > capacity) { historyRef.current.shift(); }
pointerRef.current = historyRef.current.length - 1; }
setValue(resolvedValue); }, [capacity, value])
const back = useCallback(() => { if (pointerRef.current <= 0) return;
pointerRef.current--;
setValue(historyRef.current[pointerRef.current]); }, [])
const forward = useCallback(() => { if (pointerRef.current >= historyRef.current.length - 1) return;
pointerRef.current++;
setValue(historyRef.current[pointerRef.current]); }, [])
const go = useCallback((index) => { if (index < 0 || index >= historyRef.current.length - 1) return;
pointerRef.current = index;
setValue(historyRef.current[pointerRef.current]); }, [])
return [ value, set, { history: historyRef.current, pointer: pointerRef.current, back, forward, go } ];}
export default useStateWithHistory;
API
state
: The current state.setState
: The function to update the state.stateWithHistory
: An object with the following properties:history
: An array with the history of the state.pointer
: The current position in the history.back()
: Go back in the history.forward()
: Go forward in the history.go(n: number)
: Go to a specific position in the history.