npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@custom-react-hooks/use-history-state

v1.0.1

Published

A React hook that extends useState with undo/redo functionality

Downloads

283

Readme

useHistoryState Hook

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.

Features

  • Undo/Redo Functionality: Navigate through state history with undo and redo operations.
  • History Management: Maintains a configurable history of state changes.
  • Functional Updates: Supports functional state updates like regular useState.
  • Memory Efficient: Configurable maximum history size to prevent memory leaks.
  • Type Safe: Full TypeScript support with generic typing.
  • Smart History: Prevents duplicate consecutive states from being added to history.

Installation

Installing Only Current Hooks

npm install @custom-react-hooks/use-history-state

or

yarn add @custom-react-hooks/use-history-state

Installing All Hooks

npm install @custom-react-hooks/all

or

yarn add @custom-react-hooks/all

Usage

Basic Usage

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;

Text Editor with Undo/Redo

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;

Drawing Canvas with History

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>
  );
};

Form with History

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>
  );
};

API Reference

Parameters

  • initialState (T): The initial state value.
  • options (UseHistoryStateOptions, optional): Configuration options.
    • maxHistorySize (number, optional): Maximum number of states to keep in history. Default: 50.

Returns

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.

Use Cases

  • Text Editors: Implement undo/redo functionality in text editors or rich text components.
  • Drawing Applications: Allow users to undo/redo drawing operations on canvas.
  • Form Management: Enable users to undo form changes or navigate through form states.
  • Game Development: Implement move history in games like chess or puzzle games.
  • Data Visualization: Allow users to undo changes to charts or graph configurations.
  • Image Editing: Provide undo/redo for image manipulation operations.

Behavior Notes

History Management

  • New states are added to the end of the history
  • When undoing and then setting a new state, future history is cleared
  • Duplicate consecutive states are not added to history
  • History size is limited by maxHistorySize option

Memory Management

  • Old states are automatically removed when history exceeds maxHistorySize
  • The hook prevents memory leaks by limiting history size
  • Clearing history keeps only the current state

State Updates

  • Supports both direct values and functional updates
  • Functional updates receive the current state as parameter
  • State comparisons use Object.is() for duplicate detection

Performance Considerations

  • History is stored in memory, so large objects or frequent updates may impact performance
  • Use maxHistorySize to limit memory usage
  • Consider debouncing rapid state changes for better performance
  • The hook is optimized to prevent unnecessary re-renders

TypeScript Support

The hook is fully typed and supports generic types:

const numberHistory = useHistoryState<number>(0);
const objectHistory = useHistoryState<{ count: number }>({ count: 0 });
const arrayHistory = useHistoryState<string[]>([]);

Contributing

Contributions to enhance useHistoryState are welcome. Feel free to submit issues or pull requests to the repository.

License

MIT License - see the LICENSE file for details.