import { useCallback, useEffect, useRef, useState } from "react"; interface UseUndoHook { value: T; onChange: (newValue: T) => void; undo: () => void; redo: () => void; clear: () => void; canUndo: boolean; canRedo: boolean; } function useUndo>( key: string, initialValue: T, debounceDelay: number = 400 ): UseUndoHook { const [value, setValue] = useState(initialValue); const historyRef = useRef([initialValue]); const currentPositionRef = useRef(0); const [canUndo, setCanUndo] = useState(false); const [canRedo, setCanRedo] = useState(false); const debounceTimerRef = useRef(null); // Update canUndo and canRedo useEffect(() => { setCanUndo(currentPositionRef.current > 0); setCanRedo(currentPositionRef.current < historyRef.current.length - 1); }, [value]); // Depend on value to trigger updates after undo/redo // Load initial value from localStorage useEffect(() => { const savedValue = localStorage.getItem(key); if (savedValue) { try { const parsedValue = JSON.parse(savedValue) as T; setValue(parsedValue); historyRef.current = [parsedValue]; currentPositionRef.current = 0; } catch (error) { console.error("Error parsing value from localStorage", error); } } }, [key]); const updateHistory = useCallback((newValue: T) => { const newHistory = [ ...historyRef.current.slice(0, currentPositionRef.current + 1), newValue, ]; historyRef.current = newHistory; currentPositionRef.current = newHistory.length - 1; }, []); const onChange = useCallback( (newValue: T) => { setValue(newValue); if (debounceTimerRef.current !== null) { clearTimeout(debounceTimerRef.current); } debounceTimerRef.current = setTimeout(() => { if (typeof newValue === "string" && newValue.trim()) { updateHistory(newValue); localStorage.setItem(key, JSON.stringify(newValue)); } }, debounceDelay) as unknown as number; }, [debounceDelay, updateHistory] ); const undo = useCallback(() => { const newPosition = Math.max(currentPositionRef.current - 1, 0); if (newPosition !== currentPositionRef.current) { currentPositionRef.current = newPosition; const newValue = historyRef.current[newPosition]; if (newValue !== undefined) { setValue(newValue); } localStorage.setItem(key, JSON.stringify(newValue)); } }, []); const redo = useCallback(() => { const newPosition = Math.min( currentPositionRef.current + 1, historyRef.current.length - 1 ); if (newPosition !== currentPositionRef.current) { currentPositionRef.current = newPosition; const newValue = historyRef.current[newPosition]; if (newValue !== undefined) { setValue(newValue); } localStorage.setItem(key, JSON.stringify(newValue)); } }, []); const clear = useCallback(() => { setValue(initialValue); historyRef.current = [initialValue]; currentPositionRef.current = 0; localStorage.removeItem(key); }, [initialValue, key]); return { value, onChange, undo, redo, clear, canUndo, canRedo }; } export default useUndo;