Skip to content

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

App.tsx
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

use-state-with-history.ts
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;

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.