UNPKG

@supunlakmal/hooks

Version:

A collection of reusable React hooks

66 lines 2.97 kB
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