useUndoRedo
React hook to undo and redo actions.
Made by lucas"use client";
import { useUndoRedo } from "@/components/targetblank/hooks/use-undo-redo";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { RedoIcon, UndoIcon } from "lucide-react";
import * as React from "react";
const UndoRedoDemo: React.FC = () => {
const { state, set, undo, redo, reset, canUndo, canRedo } =
useUndoRedo<string>({ initialValue: "" });
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
set(e.target.value);
};
return (
<div className="flex flex-col gap-4 w-[300px]">
<Input
value={state}
onChange={handleChange}
placeholder="Type something..."
className="w-full"
/>
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2 w-full">
<Button
variant="outline"
className="flex-1"
onClick={undo}
disabled={!canUndo}
type="button"
>
<UndoIcon className="size-4" />
</Button>
<Button
variant="outline"
className="flex-1"
onClick={redo}
disabled={!canRedo}
type="button"
>
<RedoIcon className="size-4" />
</Button>
</div>
<Button variant="destructive" onClick={() => reset("")} type="button">
Reset
</Button>
</div>
</div>
);
};
export default UndoRedoDemo;
Installation
Install the following dependencies:
Copy and paste the following code into your project:
import * as React from "react";
type UseUndoRedoOptions<T> = {
initialValue: T;
maxHistory?: number;
};
export function useUndoRedo<T>({
initialValue,
maxHistory = 100,
}: UseUndoRedoOptions<T>) {
const [past, setPast] = React.useState<T[]>([]);
const [present, setPresent] = React.useState<T>(initialValue);
const [future, setFuture] = React.useState<T[]>([]);
const set = React.useCallback(
(newValue: T) => {
setPast((prev) => {
const updated = [...prev, present];
return updated.length > maxHistory
? updated.slice(updated.length - maxHistory)
: updated;
});
setPresent(newValue);
setFuture([]); // Clear redo history
},
[present, maxHistory],
);
const undo = React.useCallback(() => {
if (past.length === 0) return;
const previous = past[past.length - 1];
setPast((prev) => prev.slice(0, prev.length - 1));
setFuture((f) => [present, ...f]);
setPresent(previous);
}, [past, present]);
const redo = React.useCallback(() => {
if (future.length === 0) return;
const next = future[0];
setFuture((f) => f.slice(1));
setPast((p) => [...p, present]);
setPresent(next);
}, [future, present]);
const reset = React.useCallback((value: T) => {
setPast([]);
setFuture([]);
setPresent(value);
}, []);
return {
state: present,
set,
undo,
redo,
reset,
canUndo: past.length > 0,
canRedo: future.length > 0,
};
}
Update the import paths to match your project setup.
Usage
const { state, set, undo, redo, reset, canUndo, canRedo } = useUndoRedo<string>(
{ initialValue: "" },
);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
set(e.target.value);
};
return (
<div>
<Input value={state} onChange={handleChange} />
</div>
);
Props
Prop | Type | Default |
---|---|---|
initialValue | string | - |
maxHistory? | number | - |