UNPKG

use-temporal-state

Version:

A lightweight React hook for undo/redo state management with history compression.

71 lines (70 loc) 3.2 kB
import { useState, useCallback } from "react"; /** * useTemporalState is a custom React hook that manages state with undo/redo capabilities, * and includes optional history compression to limit memory usage. * * @param initialValue The initial state value. * @param options Optional configuration for limiting history and controlling when changes are recorded. */ export default function useTemporalState(initialValue, options = {}) { // Destructure options with defaults const { limit = 50, shouldAddToHistory = (prev, next) => prev !== next } = options; const [past, setPast] = useState([]); // Stack of previous states for undo const [present, setPresent] = useState(initialValue); // Current state const [future, setFuture] = useState([]); // Stack of future states for redo /** * Set a new state and update past/future accordingly. * If shouldAddToHistory returns true, stores current present in past. * Resets future on new state to prevent branching. */ const set = useCallback((newState) => { // Resolve new state const next = typeof newState === "function" ? newState(present) : newState; // Only add to history if allowed by shouldAddToHistory if (!shouldAddToHistory(present, next)) { setPresent(next); return; } // Push present to past, and trim if exceeds limit const newPast = [...past, present]; const trimmedPast = newPast.length > limit ? newPast.slice(-limit) : newPast; setPast(trimmedPast); // Update past setPresent(next); // Update present setFuture([]); // Clear redo stack }, [present, past, limit, shouldAddToHistory]); /** * Undo the last change, moving present to future and restoring from past. */ const undo = useCallback(() => { if (past.length === 0) return; const newPast = past.slice(0, -1); // All except last const lastState = past[past.length - 1]; // Most recent previous state setPast(newPast); // Update past setFuture((f) => [present, ...f]); // Push current present to front of future setPresent(lastState); // Restore last state from past }, [past, present]); /** * Redo the last undone change, restoring next state from future. */ const redo = useCallback(() => { if (future.length === 0) return; const [nextState, ...restFuture] = future; // Get next state and rest const newPast = [...past, present]; // Push current state to past const trimmedPast = newPast.length > limit ? newPast.slice(-limit) : newPast; setPast(trimmedPast); // Update past setFuture(restFuture); // Remove first item from future setPresent(nextState); // Restore future state }, [future, past, present, limit]); return { state: present, // Current state set, // Function to update state undo, // Undo function redo, // Redo function canUndo: past.length > 0, // Boolean flag if undo is possible canRedo: future.length > 0, // Boolean flag if redo is possible }; }