
Security News
Software Engineering Daily Podcast: Feross on AI, Open Source, and Supply Chain Risk
Socket CEO Feross Aboukhadijeh joins Software Engineering Daily to discuss modern software supply chain attacks and rising AI-driven security risks.
@custom-react-hooks/use-history-state
Advanced tools
A React hook that extends useState with undo/redo functionality
The useHistoryState hook extends the standard useState with undo/redo functionality. It maintains a history of state changes and provides functions to navigate through the history, making it perfect for implementing undo/redo features in your React applications.
useState.npm install @custom-react-hooks/use-history-state
or
yarn add @custom-react-hooks/use-history-state
npm install @custom-react-hooks/all
or
yarn add @custom-react-hooks/all
import React from 'react';
import { useHistoryState } from '@custom-react-hooks/use-history-state';
const UndoRedoCounter = () => {
const { state, setState, undo, redo, canUndo, canRedo } = useHistoryState(0);
return (
<div>
<h2>Counter: {state}</h2>
<div>
<button onClick={() => setState(state + 1)}>Increment</button>
<button onClick={() => setState(state - 1)}>Decrement</button>
</div>
<div>
<button onClick={undo} disabled={!canUndo}>
Undo
</button>
<button onClick={redo} disabled={!canRedo}>
Redo
</button>
</div>
</div>
);
};
export default UndoRedoCounter;
import React from 'react';
import { useHistoryState } from '@custom-react-hooks/use-history-state';
const TextEditor = () => {
const {
state: text,
setState: setText,
undo,
redo,
canUndo,
canRedo,
clear,
history,
currentIndex
} = useHistoryState('', { maxHistorySize: 20 });
const handleTextChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setText(e.target.value);
};
return (
<div>
<div>
<button onClick={undo} disabled={!canUndo}>
⟲ Undo
</button>
<button onClick={redo} disabled={!canRedo}>
âźł Redo
</button>
<button onClick={clear}>
Clear History
</button>
</div>
<textarea
value={text}
onChange={handleTextChange}
placeholder="Start typing..."
rows={10}
cols={50}
/>
<div>
<p>History: {currentIndex + 1} / {history.length}</p>
<p>Can undo: {canUndo ? 'Yes' : 'No'}</p>
<p>Can redo: {canRedo ? 'Yes' : 'No'}</p>
</div>
</div>
);
};
export default TextEditor;
import React, { useRef } from 'react';
import { useHistoryState } from '@custom-react-hooks/use-history-state';
interface DrawingState {
paths: string[];
currentColor: string;
}
const DrawingCanvas = () => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const {
state,
setState,
undo,
redo,
canUndo,
canRedo
} = useHistoryState<DrawingState>({
paths: [],
currentColor: '#000000'
});
const addPath = (path: string) => {
setState(prevState => ({
...prevState,
paths: [...prevState.paths, path]
}));
};
const changeColor = (color: string) => {
setState(prevState => ({
...prevState,
currentColor: color
}));
};
return (
<div>
<div>
<button onClick={undo} disabled={!canUndo}>
Undo
</button>
<button onClick={redo} disabled={!canRedo}>
Redo
</button>
<input
type="color"
value={state.currentColor}
onChange={(e) => changeColor(e.target.value)}
/>
</div>
<canvas
ref={canvasRef}
width={400}
height={300}
style={{ border: '1px solid #ccc' }}
/>
<p>Paths drawn: {state.paths.length}</p>
</div>
);
};
import React from 'react';
import { useHistoryState } from '@custom-react-hooks/use-history-state';
interface FormData {
name: string;
email: string;
message: string;
}
const FormWithHistory = () => {
const { state, setState, undo, redo, canUndo, canRedo } = useHistoryState<FormData>({
name: '',
email: '',
message: ''
});
const updateField = (field: keyof FormData, value: string) => {
setState(prev => ({ ...prev, [field]: value }));
};
return (
<div>
<div>
<button onClick={undo} disabled={!canUndo}>
Undo Changes
</button>
<button onClick={redo} disabled={!canRedo}>
Redo Changes
</button>
</div>
<form>
<div>
<label>Name:</label>
<input
value={state.name}
onChange={(e) => updateField('name', e.target.value)}
/>
</div>
<div>
<label>Email:</label>
<input
value={state.email}
onChange={(e) => updateField('email', e.target.value)}
/>
</div>
<div>
<label>Message:</label>
<textarea
value={state.message}
onChange={(e) => updateField('message', e.target.value)}
/>
</div>
</form>
</div>
);
};
initialState (T): The initial state value.options (UseHistoryStateOptions, optional): Configuration options.
maxHistorySize (number, optional): Maximum number of states to keep in history. Default: 50.An object containing:
state (T): The current state value.setState (function): Function to update the state (similar to useState).undo (function): Function to undo the last state change.redo (function): Function to redo the next state change.canUndo (boolean): Whether undo operation is possible.canRedo (boolean): Whether redo operation is possible.clear (function): Function to clear the history (keeps current state).history (T[]): Array of all states in history.currentIndex (number): Current position in the history.maxHistorySize optionmaxHistorySizeObject.is() for duplicate detectionmaxHistorySize to limit memory usageThe hook is fully typed and supports generic types:
const numberHistory = useHistoryState<number>(0);
const objectHistory = useHistoryState<{ count: number }>({ count: 0 });
const arrayHistory = useHistoryState<string[]>([]);
Contributions to enhance useHistoryState are welcome. Feel free to submit issues or pull requests to the repository.
MIT License - see the LICENSE file for details.
FAQs
A React hook that extends useState with undo/redo functionality
We found that @custom-react-hooks/use-history-state demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Socket CEO Feross Aboukhadijeh joins Software Engineering Daily to discuss modern software supply chain attacks and rising AI-driven security risks.

Security News
GitHub has revoked npm classic tokens for publishing; maintainers must migrate, but OpenJS warns OIDC trusted publishing still has risky gaps for critical projects.

Security News
Rust’s crates.io team is advancing an RFC to add a Security tab that surfaces RustSec vulnerability and unsoundness advisories directly on crate pages.