Vasyl Putra
Implementing a Undo/Redo Hook in React
Undo/Redo is a commonly used feature in many applications, enhancing user experience by allowing changes to be reverted. React Hooks provide a powerful way to manage state and handle changes in React components.
useUndoableState.tsx1import { SetStateAction, useCallback, useState } from "react";2import { unstable_batchedUpdates } from "react-dom";34interface IOptions {5historyMaxLength?: number;6}78/**9* useUndoableState is a custom hook that allows you to manage state with undo and redo functionality.10* It takes an initial state and an options object as parameters.11* The options object can have a historyMaxLength property which specifies the maximum number of states to store in the history.12* The hook returns an object with the current state, a setState function to update the state,13* a resetState function to reset the state to a new value,14* undo and redo functions to move back and forth in the history,15* and the undoHistory and redoHistory arrays that store the history of states.16*/17export const useUndoableState = <T>(18initialState: T | (() => T),19{ historyMaxLength }: IOptions = {},20) => {21// Define the state and its setter function22const [state, setState] = useState<T>(initialState);2324// Define the undo and redo history arrays25const [undoHistory, setUndoHistory] = useState<T[]>([]);26const [redoHistory, setRedoHistory] = useState<T[]>([]);2728// Define the undo and redo functions29const undo = useCallback(() => {30// If there is anything in the undo history, restore the previous state31if (undoHistory.length) {32const stateToRestore = undoHistory.pop()!;3334// Update the undo and redo history arrays35setUndoHistory([...undoHistory]);36setRedoHistory([...redoHistory, state]);3738// Update the state39setState(stateToRestore);40}41}, [undoHistory, redoHistory, state]);4243const redo = useCallback(() => {44// If there is anything in the redo history, restore the next state45if (redoHistory.length) {46const stateToRestore = redoHistory.pop()!;4748// Update the undo and redo history arrays49setUndoHistory([...undoHistory, state]);50setRedoHistory([...redoHistory]);5152// Update the state53setState(stateToRestore);54}55}, [state, redoHistory, undoHistory]);5657// Define the setState function that updates the state and adds the previous state to the undo history58const handleSetState = useCallback(59(stateOrStateSetter: SetStateAction<T>) => {60// Use batchedUpdates to group multiple state updates into a single update61unstable_batchedUpdates(() => {62// Update the undo history by adding the current state to it63setUndoHistory(64[...undoHistory, state].slice(-Number(historyMaxLength)),65);6667// Reset the redo history68setRedoHistory([]);6970// Update the state71setState(stateOrStateSetter);72});73},74[historyMaxLength, state, undoHistory],75);7677// Define the resetState function that resets the state to a new value78const resetState = useCallback((stateOrStateSetter: SetStateAction<T>) => {79// Use batchedUpdates to group multiple state updates into a single update80unstable_batchedUpdates(() => {81// Reset the undo and redo history82setUndoHistory([]);83setRedoHistory([]);8485// Update the state86setState(stateOrStateSetter);87});88}, []);8990// Return the state, setState, resetState, undo, redo, undoHistory, and redoHistory objects91return {92state,93setState: handleSetState,94resetState,95undo,96undoHistory,97redo,98redoHistory,99};100};