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 🙏

© 2026 – Pkg Stats / Ryan Hefner

use-good-hooks

v1.0.42

Published

![React](https://img.shields.io/badge/React-19.0.0+-61DAFB?style=flat-square&logo=react&logoColor=white) ![TypeScript](https://img.shields.io/badge/TypeScript-5.8+-3178C6?style=flat-square&logo=typescript&logoColor=white) ![Vite](https://img.shields.io/ba

Downloads

262

Readme

🪝 use-good-hooks

React TypeScript Vite License Vitest

A collection of well-tested, performance-optimized React hooks for common web application patterns. These hooks help manage state, UI interactions, and browser features with clean, reusable abstractions.

📦 Installation

# Using npm
npm install use-good-hooks

# Using yarn
yarn add use-good-hooks

# Using pnpm
pnpm add use-good-hooks

🔌 Requirements

  • React 19.0.0+
  • Lodash 4.17.21+

🧰 Available Hooks

useDebounce

Debounces value changes to prevent rapid updates. Useful for search inputs, form validation, and other scenarios where you want to delay state updates until after a user has stopped changing the input.

import useDebounce from 'use-good-hooks/use-debounce';

const SearchComponent = () => {
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearchTerm = useDebounce(searchTerm, 500); // 500ms delay

  // API call will only happen 500ms after the user stops typing
  useEffect(() => {
    if (debouncedSearchTerm) {
      searchAPI(debouncedSearchTerm);
    }
  }, [debouncedSearchTerm]);

  return (
    <input
      type="text"
      value={searchTerm}
      onChange={(e) => setSearchTerm(e.target.value)}
      placeholder="Search..."
    />
  );
};

Parameters

  • value: The value to debounce
  • delay: (Optional) The delay in milliseconds (default: 300ms)

Returns

  • The debounced value

useDebounceFn

Creates a debounced version of a function. This hook ensures that a function is only executed after a specified period of inactivity, preventing it from being called too frequently. It's ideal for handling events like button clicks or API triggers that should not fire on every user action.

import useDebounceFn from 'use-good-hooks/use-debounce-fn';

const SaveButton = () => {
  const [status, setStatus] = useState('Idle');

  const debouncedSave = useDebounceFn(() => {
    setStatus('Saving...');
    // Simulate API call
    setTimeout(() => setStatus('Saved!'), 1000);
  }, 1000); // 1000ms delay

  const handleClick = () => {
    setStatus('Waiting...');
    debouncedSave();
  };

  return (
    <div>
      <button onClick={handleClick}>Save Changes</button>
      <p>Status: {status}</p>
    </div>
  );
};

Parameters

  • fn: The function to debounce
  • delay: (Optional) The delay in milliseconds (default: 300ms)

Returns

  • The debounced function.

useThrottle

Limits the rate at which a value can update. Useful for scroll events, window resizing, and other high-frequency events.

import useThrottle from 'use-good-hooks/use-throttle';

const ScrollTracker = () => {
  const [scrollY, setScrollY] = useState(0);
  const throttledScrollY = useThrottle(scrollY, 200); // 200ms throttle

  useEffect(() => {
    const handleScroll = () => {
      setScrollY(window.scrollY);
    };

    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);

  return <div>Throttled scroll position: {throttledScrollY}px</div>;
};

Parameters

  • value: The value to throttle
  • delay: (Optional) The throttle interval in milliseconds (default: 300ms)

Returns

  • The throttled value

useThrottleFn

Creates a throttled version of a function, limiting its execution to at most once per specified interval. It is useful for performance-critical scenarios like handling mouse movements, scrolling, or window resizing events without overwhelming the browser.

import useThrottleFn from 'use-good-hooks/use-throttle-fn';

const MouseTracker = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const throttledMouseMove = useThrottleFn((event) => {
    setPosition({ x: event.clientX, y: event.clientY });
  }, 300); // Update at most every 300ms

  useEffect(() => {
    window.addEventListener('mousemove', throttledMouseMove);
    return () => window.removeEventListener('mousemove', throttledMouseMove);
  }, [throttledMouseMove]);

  return (
    <div>
      Throttled mouse position: X: {position.x}, Y: {position.y}
    </div>
  );
};

Parameters

  • fn: The function to throttle
  • delay: (Optional) The throttle interval in milliseconds (default: 300ms)

Returns

  • The throttled function.

usePrev

Captures the previous value of a state or prop. Useful for comparing changes between renders.

import usePrev from 'use-good-hooks/use-prev';

const Counter = ({ count }) => {
  const prevCount = usePrev(count);

  return (
    <div>
      <p>Current count: {count}</p>
      <p>Previous count: {prevCount ?? 'None'}</p>
      <p>Direction: {count > prevCount ? 'Increasing' : count < prevCount ? 'Decreasing' : 'No change'}</p>
    </div>
  );
};

Parameters

  • value: The value to track

Returns

  • The previous value (undefined on first render)

useHistoryState

Tracks the history of a state value, providing undo and redo capabilities. This is perfect for building editors, forms, or any UI where users might want to reverse their actions.

import useHistoryState from 'use-good-hooks/use-history-state';

const TextEditor = () => {
  const [state, actions] = useHistoryState('', {
    maxCapacity: 10,
    debounceMs: 0 // Disable debouncing for immediate updates
  });

  return (
    <div>
      <textarea
        value={state.present}
        onChange={(e) => actions.set(e.target.value)}
        rows={4}
        cols={50}
      />
      <div>
        <button onClick={actions.undo} disabled={!state.canUndo}>
          Undo
        </button>
        <button onClick={actions.redo} disabled={!state.canRedo}>
          Redo
        </button>
        <button onClick={actions.initial}>Initial State</button>
      </div>
      <p>Past states: {state.past.length}</p>
      <p>Future states: {state.future.length}</p>
    </div>
  );
};

Parameters

  • initialState: The initial state value
  • options: (Optional) Configuration options:
    • debounceMs: Time in milliseconds to debounce the state changes (default: 250)
    • debounceSettings: Debounce settings object from Lodash
    • maxCapacity: Maximum number of history entries to keep (default: 10)
    • onChange: Function to call when the state changes, receives { action, state }
    • paused: Boolean indicating if the history is paused (default: false)

Returns

Returns a tuple [historyState, historyActions]:

historyState (Object): - canRedo: Boolean indicating if redo is possible - canUndo: Boolean indicating if undo is possible - future: Array of future states (for redo) - past: Array of past states (for undo) - paused: Boolean indicating if the history is paused - present: The current state value

historyActions (Object): - initial: Function to reset to initial state - pause: Function to pause history tracking (updates won't be recorded) - redo: Function to move to the next state (redo) - replace: Function to replace the state without adding to history - resume: Function to resume history tracking - set: Function to update the state and record history (debounced by default) - setDirect: Function to update the state immediately, bypassing debounce - undo: Function to move to the previous state (undo)

Example with onChange callback

const Editor = () => {
  const [state, actions] = useHistoryState('', {
    onChange: ({ action, state }) => {
      console.log(`Action: ${action}, State: ${state}`);
      // Action can be: 'SET', 'UNDO', 'REDO', 'INITIAL', 'REPLACE'
    }
  });

  return (
    <textarea
      value={state.present}
      onChange={(e) => actions.set(e.target.value)}
    />
  );
};

Example with pause/resume

const Form = () => {
  const [state, actions] = useHistoryState({ name: '', email: '' });

  const handleBulkUpdate = () => {
    actions.pause(); // Pause history tracking
    actions.set({ name: 'John', email: '[email protected]' });
    actions.set({ name: 'Jane', email: '[email protected]' });
    actions.resume(); // Resume history tracking
    // Only the final state will be in history
  };

  return (
    <div>
      <input
        value={state.present.name}
        onChange={(e) => actions.set({ ...state.present, name: e.target.value })}
      />
      <button onClick={handleBulkUpdate}>Bulk Update</button>
    </div>
  );
};

useDistinct

Detects distinct changes in values with support for deep comparison and custom equality checks. Useful for tracking whether complex objects have actually changed.

import useDistinct from 'use-good-hooks/use-distinct';

const UserProfileForm = ({ user }) => {
  const { distinct, value, prevValue } = useDistinct(user, { deep: true });

  useEffect(() => {
    if (distinct) {
      console.log('User data changed from:', prevValue, 'to:', value);
      // Perhaps save to backend or update UI
    }
  }, [distinct, prevValue, value]);

  return (
    <div>
      <h2>Editing profile for: {user.name}</h2>
      {distinct && <div className="alert">Unsaved changes!</div>}
      {/* Form inputs */}
    </div>
  );
};

Parameters

  • inputValue: The value to check for changes
  • options: (Optional) Configuration options:
    • deep: Boolean to enable deep equality comparison (default: false)
    • compare: Custom comparison function (a, b) => boolean
    • debounce: Debounce time in milliseconds (default: 0)

Returns

  • Object with:
    • distinct: Boolean indicating if the value changed
    • prevValue: The previous distinct value
    • value: The current value

useStorageState

Persists state to localStorage or sessionStorage with automatic serialization/deserialization.

import useStorageState from 'use-good-hooks/use-storage-state';

const ThemePreferences = () => {
  const [preferences, setPreferences, { removeKey }] = useStorageState('theme-prefs', {
    darkMode: false,
    fontSize: 'medium',
    compactView: true
  });

  return (
    <div>
      <h2>Theme Settings</h2>
      <label>
        <input
          type="checkbox"
          checked={preferences.darkMode}
          onChange={() => setPreferences({...preferences, darkMode: !preferences.darkMode})}
        />
        Dark Mode
      </label>
      {/* More settings */}
      <button onClick={removeKey}>Reset to Defaults</button>
    </div>
  );
};

Parameters

  • key: Storage key name
  • initialState: Default state if no stored value exists
  • options: (Optional) Configuration options:
    • storage: 'local' or 'session' (default: 'local')
    • debounce: Debounce time in milliseconds (default: 500)
    • onError: Error callback function
    • omitKeys: Array of keys to omit from storage or function (value, key) => boolean
    • pickKeys: Array of keys to include in storage or function (value, key) => boolean

Returns

  • Array with:
    • State value
    • State setter function
    • Object with utility functions:
      • removeKey: Function to clear the storage key

useUrlState

Synchronizes state with URL query parameters. Great for shareable UI states, filters, pagination, and search terms.

import useUrlState from 'use-good-hooks/use-url-state';

const ProductFilter = () => {
  const [filters, setFilters] = useUrlState({
    category: '',
    minPrice: 0,
    maxPrice: 1000,
    sortBy: 'newest'
  }, {
    url: new URL(window.location.href),
    kebabCase: true,
    omitValues: ['', 0]
  });

  return (
    <div>
      <select
        value={filters.category}
        onChange={(e) => setFilters({...filters, category: e.target.value})}
      >
        <option value="">All Categories</option>
        <option value="electronics">Electronics</option>
        <option value="clothing">Clothing</option>
      </select>
      {/* More filter controls */}
    </div>
  );
};

Parameters

  • initialState: Default state if URL has no parameters
  • options: Configuration options:
    • url: URL object to use (required)
    • debounce: Debounce time in milliseconds (default: 500)
    • kebabCase: Convert camelCase keys to kebab-case in URL (default: true)
    • prefix: Optional prefix for URL parameters
    • onError: Error callback function
    • omitKeys: Array of keys to omit from URL or function (value, key) => boolean
    • pickKeys: Array of keys to include in URL or function (value, key) => boolean
    • omitValues: Array of values to omit from URL or function (value, key) => boolean

Returns

  • Array with:
    • State value
    • State setter function

useGlobalState and createGlobalState

Creates and manages global state that can be shared across components with automatic synchronization.

import { createGlobalState, useGlobalState } from 'use-good-hooks/use-global-state';

// Create a global state instance (typically in a separate file)
const counterState = createGlobalState({ count: 0 });

// Component A
const CounterDisplay = () => {
  const [counter, setCounter] = useGlobalState(counterState);

  return (
    <div>
      <p>Count: {counter.count}</p>
      <button onClick={() => setCounter({ count: counter.count + 1 })}>
        Increment
      </button>
    </div>
  );
};

// Component B (in a different part of your app)
const CounterActions = () => {
  const [counter, setCounter] = useGlobalState(counterState);

  return (
    <div>
      <button onClick={() => setCounter({ count: 0 })}>
        Reset Count
      </button>
      <button onClick={() => setCounter(prev => ({ count: prev.count + 5 }))}>
        Add 5
      </button>
    </div>
  );
};

Usage

  1. First, create a global state store:
// state/counter.ts
import { createGlobalState } from 'use-good-hooks/use-global-state';

export const counterState = createGlobalState({ count: 0 });
  1. Then use it in any component:
import { useGlobalState } from 'use-good-hooks/use-global-state';
import { counterState } from './state/counter';

const MyComponent = () => {
	const [counter, setCounter] = useGlobalState(counterState);
	// ...
};

createGlobalState Parameters

  • initialState: The initial state value

createGlobalState Returns

  • Array with:
    • state: The current state
    • setState: Function to update state
    • store: Object with utility methods:
      • getState(): Function to get current state
      • subscribe(callback): Subscribe to state changes
      • resetState(): Reset to initial state

useGlobalState Parameters

  • globalState: The global state created with createGlobalState

useGlobalState Returns

  • Array with:
    • state: The component's local copy of the state
    • setState: Function to update global state (accepts new value or update function)

useTemporaryState

Creates a temporary state that resets after a specified timeout.

import useTemporaryState from 'use-good-hooks/use-temporary-state';

const [state, setState] = useTemporaryState('initial', 1000);

// State will reset to 'initial' after 1 second

Parameters

  • initialState: The initial state value
  • ms: The timeout duration in milliseconds (default: 3000)

Returns

  • Array with:
    • state: The current state
    • setState: Function to update state

useLateState

Delays the update of a state value until a specified time has passed. This is useful for scenarios where you want to introduce a delay before a state change takes effect, such as showing a loading spinner for a minimum amount of time.

import useLateState from 'use-good-hooks/use-late-state';

const DelayedComponent = () => {
  const [status, setStatus, cancelUpdate] = useLateState('Waiting...', 2000); // 2-second delay

  const handleUpdate = () => {
    setStatus('Updated!');
  };

  const handleImmediateUpdate = () => {
    setStatus('Immediately Updated!', true);
  };

  const handleCancel = () => {
    const wasCancelled = cancelUpdate();
    if (wasCancelled) {
      alert('Update cancelled!');
    }
  };

  return (
    <div>
      <p>Status: {status}</p>
      <button onClick={handleUpdate}>Update after 2s</button>
      <button onClick={handleImmediateUpdate}>Update Immediately</button>
      <button onClick={handleCancel}>Cancel Update</button>
    </div>
  );
};

Parameters

  • initial: The initial value of the state.
  • delay: The delay in milliseconds before the state is updated.

Returns

  • value: The current state value.
  • setLate: A function to update the state after the specified delay. It can also accept a second boolean argument to update the state immediately.
  • cancel: A function to cancel a pending state update.

🧪 Running Tests

This library is thoroughly tested with Vitest and React Testing Library. To run the tests:

# Using npm
npm test

# Using yarn
yarn test

# Using pnpm
pnpm test

🔄 How hooks update and optimize performance

Each hook in this library is designed with performance in mind:

  1. useDebounce and useThrottle reduce unnecessary renders using Lodash's optimized implementations
  2. useDistinct avoids reference equality problems with optional deep comparison
  3. useStorageState batches storage updates to reduce expensive serialization/deserialization
  4. useUrlState efficiently handles URL synchronization with debouncing
  5. useGlobalState and createGlobalState provide a way to share state across components with automatic synchronization
  6. useTemporaryState allows for temporary state that resets after a timeout
  7. useLateState provides a mechanism to delay state updates, which can be useful for managing UI transitions and asynchronous operations.

🛠️ Development

# Install dependencies
yarn install

# Start development server
yarn dev

# Run tests
yarn test

# Build the library
yarn build

# Lint and format the code
yarn lint

🙏 Acknowledgements

This project was built with:

📝 License

MIT © Felipe Rohde

👨‍💻 Author

Felipe Rohde