@supunlakmal/hooks
Version:
A collection of reusable React hooks
66 lines • 2.97 kB
JavaScript
import { useState, useRef, useCallback } from 'react';
/**
* Custom hook that manages state like `useState` but keeps a history of changes,
* allowing for undo (back) and redo (forward) operations.
*
* @template T The type of the state.
* @param initialState - The initial value for the state.
* @param capacity - The maximum number of history entries to keep (default: 10).
* @returns An object containing the current state, state setter, history, navigation functions, and flags.
*/
export const useStateWithHistory = (initialState, capacity = 10) => {
const [state, setInternalState] = useState(initialState);
const historyRef = useRef([initialState]);
const pointerRef = useRef(0); // Points to the current state index in history
// Use refs for capacity to avoid triggering updates if capacity changes
const capacityRef = useRef(capacity);
capacityRef.current = capacity; // Keep it updated if capacity prop changes
const setState = useCallback((newStateOrFn) => {
setInternalState((currentState) => {
const newState = typeof newStateOrFn === 'function'
? newStateOrFn(currentState)
: newStateOrFn;
// If the pointer is not at the end of history (i.e., after an undo),
// subsequent state changes should overwrite the future history.
if (pointerRef.current < historyRef.current.length - 1) {
historyRef.current.splice(pointerRef.current + 1);
}
// Add the new state to history
historyRef.current.push(newState);
// Maintain capacity: remove the oldest entry if needed
while (historyRef.current.length > capacityRef.current) {
historyRef.current.shift(); // Remove the first (oldest) element
}
// Update the pointer to the new latest state
pointerRef.current = historyRef.current.length - 1;
return newState; // Return the new state for React's useState
});
}, []); // No dependencies needed as refs handle capacity
const go = useCallback((index) => {
const newPointer = Math.max(0, Math.min(historyRef.current.length - 1, index));
if (newPointer === pointerRef.current)
return; // No change
pointerRef.current = newPointer;
setInternalState(historyRef.current[pointerRef.current]);
}, []);
const back = useCallback(() => {
go(pointerRef.current - 1);
}, [go]);
const forward = useCallback(() => {
go(pointerRef.current + 1);
}, [go]);
const canUndo = pointerRef.current > 0;
const canRedo = pointerRef.current < historyRef.current.length - 1;
return {
state,
setState,
history: historyRef.current,
pointer: pointerRef.current,
back,
forward,
go,
canUndo,
canRedo,
};
};
//# sourceMappingURL=useStateWithHistory.js.map