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

@commons-dev/react-hooks

v1.0.5

Published

A collection of useful React hooks

Readme

@commons-dev/react-hooks

A collection of useful React hooks built with TypeScript. This package provides tree-shakeable hooks that can be imported individually to keep your bundle size minimal.

Installation

npm install @commons-dev/react-hooks
yarn add @commons-dev/react-hooks
pnpm add @commons-dev/react-hooks

Features

  • 🎯 Tree-shakeable - Import only what you need
  • 📦 TypeScript - Full TypeScript support
  • 🚀 Lightweight - Minimal dependencies
  • Well-tested - Comprehensive test coverage
  • 📖 Well-documented - Clear API documentation

Hooks

useDebounce

Debounces a value or function, delaying updates until after a specified period of inactivity. This is useful for reducing the frequency of expensive operations like API calls or DOM updates.

How it works: When debouncing a value, the hook waits for the value to stop changing for the specified delay before updating. When debouncing a function, the function will only execute after the delay period has passed since the last call.

Debouncing a value:

import { useDebounce } from '@commons-dev/react-hooks';
import { useState, useEffect } from 'react';

function SearchComponent() {
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearchTerm = useDebounce(searchTerm, 300);

  useEffect(() => {
    // This will only run after user stops typing for 300ms
    if (debouncedSearchTerm) {
      performSearch(debouncedSearchTerm);
    }
  }, [debouncedSearchTerm]);

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

Debouncing a function:

import { useDebounce } from '@commons-dev/react-hooks';

function SearchComponent() {
  const handleSearch = useDebounce((term: string) => {
    performSearch(term);
  }, 300);

  return (
    <input
      onChange={(e) => handleSearch(e.target.value)}
      placeholder="Search..."
    />
  );
}

API:

// Debounce a value
function useDebounce<T>(value: T, delay?: number): T;

// Debounce a function
function useDebounce<T extends (...args: unknown[]) => unknown>(
  fn: T,
  delay?: number
): T;

Parameters:

  • value - The value to debounce, OR
  • fn - The function to debounce
  • delay - The delay in milliseconds (default: 500)

Returns:

  • The debounced value or debounced function

Use cases:

  • Search input fields (wait for user to finish typing)
  • Window resize handlers
  • Form validation (validate after user stops typing)
  • API calls triggered by user input

useThrottle

Throttles a value or function, limiting how often it can update or execute. Unlike debouncing, throttling ensures the function/value updates at regular intervals, not just after inactivity.

How it works: When throttling a value, the hook updates the value at most once per delay period. When throttling a function, the function will execute at most once per delay period, even if called multiple times.

Throttling a value:

import { useThrottle } from '@commons-dev/react-hooks';
import { useState, useEffect } from 'react';

function ScrollComponent() {
  const [scrollY, setScrollY] = useState(0);
  const throttledScrollY = useThrottle(scrollY, 100);

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

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

  useEffect(() => {
    // This will run at most once every 100ms
    updateUI(throttledScrollY);
  }, [throttledScrollY]);

  return <div>Scroll position: {throttledScrollY}</div>;
}

Throttling a function:

import { useThrottle } from '@commons-dev/react-hooks';
import { useEffect } from 'react';

function ScrollComponent() {
  const handleScroll = useThrottle((event: Event) => {
    updateUI(event);
  }, 100);

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

  return <div>Scroll component</div>;
}

API:

// Throttle a value
function useThrottle<T>(value: T, delay?: number): T;

// Throttle a function
function useThrottle<T extends (...args: unknown[]) => unknown>(
  fn: T,
  delay?: number
): T;

Parameters:

  • value - The value to throttle, OR
  • fn - The function to throttle
  • delay - The delay in milliseconds (default: 500)

Returns:

  • The throttled value or throttled function

Use cases:

  • Scroll event handlers
  • Mouse move events
  • Window resize handlers
  • Real-time data updates (e.g., stock prices, live feeds)

Difference from debounce:

  • Debounce: Waits for inactivity before executing (good for search inputs)
  • Throttle: Executes at regular intervals (good for scroll/resize events)

useLocalStorage

Synchronizes component state with localStorage, persisting data across browser sessions. The hook automatically handles serialization/deserialization and listens for storage events from other tabs/windows.

How it works: The hook reads from localStorage on mount and writes to it whenever the state changes. It also listens for storage events to keep the state in sync across multiple tabs/windows. The hook is SSR-safe and will gracefully handle cases where localStorage is unavailable.

Basic usage:

import { useLocalStorage } from '@commons-dev/react-hooks';

function PreferencesComponent() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');

  return (
    <div>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle Theme
      </button>
      <p>Current theme: {theme}</p>
    </div>
  );
}

With complex objects:

import { useLocalStorage } from '@commons-dev/react-hooks';

interface UserPreferences {
  theme: 'light' | 'dark';
  language: string;
  notifications: boolean;
}

function SettingsComponent() {
  const [preferences, setPreferences] = useLocalStorage<UserPreferences>(
    'userPreferences',
    {
      theme: 'light',
      language: 'en',
      notifications: true,
    }
  );

  const updateTheme = (newTheme: 'light' | 'dark') => {
    setPreferences({ ...preferences, theme: newTheme });
  };

  // Or use functional update
  const toggleNotifications = () => {
    setPreferences((prev) => ({
      ...prev,
      notifications: !prev.notifications,
    }));
  };

  return (
    <div>
      <button onClick={() => updateTheme('dark')}>Dark Mode</button>
      <button onClick={toggleNotifications}>
        {preferences.notifications ? 'Disable' : 'Enable'} Notifications
      </button>
    </div>
  );
}

API:

function useLocalStorage<T>(
  key: string,
  initialValue: T
): [T, (value: T | ((val: T) => T)) => void];

Parameters:

  • key - The localStorage key to store the value under
  • initialValue - The initial value to use if the key doesn't exist in localStorage

Returns:

  • A tuple [storedValue, setValue] similar to useState
    • storedValue - The current value from localStorage (or initialValue if not found)
    • setValue - A function to update the value. Accepts either a new value or a function that receives the previous value and returns the new value

Features:

  • ✅ Automatic JSON serialization/deserialization
  • ✅ SSR-safe (handles server-side rendering gracefully)
  • ✅ Cross-tab synchronization (listens to storage events)
  • ✅ Error handling (logs errors if localStorage operations fail)
  • ✅ Supports functional updates (like useState)

Notes:

  • Values are stored as JSON, so they must be JSON-serializable
  • The hook will use initialValue if localStorage is unavailable (e.g., in SSR or private browsing mode)
  • Changes made in other tabs/windows will automatically update the state

useSessionStorage

Synchronizes component state with sessionStorage, persisting data for the current browser session. Unlike localStorage, data stored in sessionStorage is cleared when the browser tab is closed.

How it works: The hook reads from sessionStorage on mount and writes to it whenever the state changes. It also listens for storage events to keep the state in sync across multiple tabs/windows (only for the same session). The hook is SSR-safe and will gracefully handle cases where sessionStorage is unavailable.

Basic usage:

import { useSessionStorage } from '@commons-dev/react-hooks';

function SessionComponent() {
  const [sessionId, setSessionId] = useSessionStorage('sessionId', '');

  useEffect(() => {
    if (!sessionId) {
      setSessionId(generateSessionId());
    }
  }, [sessionId, setSessionId]);

  return <div>Session ID: {sessionId}</div>;
}

With complex objects:

import { useSessionStorage } from '@commons-dev/react-hooks';

interface FormData {
  name: string;
  email: string;
  message: string;
}

function ContactForm() {
  const [formData, setFormData] = useSessionStorage<FormData>(
    'contactForm',
    {
      name: '',
      email: '',
      message: '',
    }
  );

  const updateField = (field: keyof FormData, value: string) => {
    setFormData((prev) => ({ ...prev, [field]: value }));
  };

  return (
    <form>
      <input
        value={formData.name}
        onChange={(e) => updateField('name', e.target.value)}
        placeholder="Name"
      />
      <input
        value={formData.email}
        onChange={(e) => updateField('email', e.target.value)}
        placeholder="Email"
      />
      <textarea
        value={formData.message}
        onChange={(e) => updateField('message', e.target.value)}
        placeholder="Message"
      />
    </form>
  );
}

API:

function useSessionStorage<T>(
  key: string,
  initialValue: T
): [T, (value: T | ((val: T) => T)) => void];

Parameters:

  • key - The sessionStorage key to store the value under
  • initialValue - The initial value to use if the key doesn't exist in sessionStorage

Returns:

  • A tuple [storedValue, setValue] similar to useState
    • storedValue - The current value from sessionStorage (or initialValue if not found)
    • setValue - A function to update the value. Accepts either a new value or a function that receives the previous value and returns the new value

Features:

  • ✅ Automatic JSON serialization/deserialization
  • ✅ SSR-safe (handles server-side rendering gracefully)
  • ✅ Cross-tab synchronization (listens to storage events from sessionStorage)
  • ✅ Error handling (logs errors if sessionStorage operations fail)
  • ✅ Supports functional updates (like useState)

Notes:

  • Values are stored as JSON, so they must be JSON-serializable
  • The hook will use initialValue if sessionStorage is unavailable (e.g., in SSR or private browsing mode)
  • Data is cleared when the browser tab is closed (unlike localStorage)
  • Changes made in other tabs/windows will automatically update the state (for the same session)

Difference from useLocalStorage:

  • useLocalStorage: Data persists across browser sessions (until explicitly cleared)
  • useSessionStorage: Data is cleared when the browser tab is closed

useCookie

Synchronizes component state with browser cookies, allowing you to store and retrieve data that persists across browser sessions and can be sent to the server.

How it works: The hook reads from cookies on mount and writes to them whenever the state changes. Cookies are automatically serialized/deserialized as JSON. The hook supports all standard cookie options including expiration, path, domain, secure, and sameSite.

Basic usage:

import { useCookie } from '@commons-dev/react-hooks';

function ThemeComponent() {
  const [theme, setTheme, removeTheme] = useCookie('theme', 'light');

  return (
    <div>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle Theme
      </button>
      <button onClick={removeTheme}>Reset Theme</button>
      <p>Current theme: {theme}</p>
    </div>
  );
}

With cookie options:

import { useCookie } from '@commons-dev/react-hooks';

function PreferencesComponent() {
  const [preferences, setPreferences] = useCookie(
    'userPreferences',
    { language: 'en', timezone: 'UTC' },
    {
      expires: 30, // 30 days
      path: '/',
      secure: true,
      sameSite: 'strict',
    }
  );

  const updateLanguage = (lang: string) => {
    setPreferences({ ...preferences, language: lang });
  };

  return (
    <div>
      <select
        value={preferences.language}
        onChange={(e) => updateLanguage(e.target.value)}
      >
        <option value="en">English</option>
        <option value="es">Spanish</option>
        <option value="fr">French</option>
      </select>
    </div>
  );
}

With per-call options:

import { useCookie } from '@commons-dev/react-hooks';

function AuthComponent() {
  const [token, setToken] = useCookie('authToken', '');

  const handleLogin = (newToken: string) => {
    // Set cookie with specific options for this call
    setToken(newToken, {
      expires: 7, // 7 days
      path: '/',
      secure: true,
      sameSite: 'strict',
    });
  };

  return <button onClick={() => handleLogin('abc123')}>Login</button>;
}

API:

function useCookie<T>(
  key: string,
  initialValue: T,
  options?: CookieOptions
): [T, (value: T | ((val: T) => T), cookieOptions?: CookieOptions) => void, () => void];

Parameters:

  • key - The cookie key to store the value under
  • initialValue - The initial value to use if the cookie doesn't exist
  • options - Optional cookie options (applied to all cookie operations unless overridden)
    • expires - Expiration date (Date object) or number of days (number)
    • path - Cookie path (default: current path)
    • domain - Cookie domain
    • secure - Whether the cookie should only be sent over HTTPS
    • sameSite - SameSite attribute: 'strict', 'lax', or 'none'

Returns:

  • A tuple [storedValue, setValue, removeValue]
    • storedValue - The current value from the cookie (or initialValue if not found)
    • setValue - A function to update the cookie. Accepts either a new value or a function that receives the previous value and returns the new value. Can optionally accept cookie options for this specific call
    • removeValue - A function to remove the cookie and reset to initialValue

Features:

  • ✅ Automatic JSON serialization/deserialization
  • ✅ SSR-safe (handles server-side rendering gracefully)
  • ✅ Supports all standard cookie options
  • ✅ Per-call cookie options (override default options)
  • ✅ Easy cookie removal
  • ✅ Error handling (logs errors if cookie operations fail)
  • ✅ Supports functional updates (like useState)

Notes:

  • Values are stored as JSON, so they must be JSON-serializable
  • The hook will use initialValue if cookies are unavailable (e.g., in SSR)
  • Cookie size is limited (typically 4KB per cookie)
  • Cookies are sent to the server with every HTTP request (consider security implications)
  • Domain restrictions in test environments may prevent setting cookies with custom domains

Use cases:

  • User preferences that need to persist across sessions
  • Authentication tokens
  • Theme/language preferences
  • Analytics tracking
  • Feature flags

useClickOutside

Detects clicks outside a referenced element, commonly used for closing modals, dropdowns, or popovers when users click outside of them.

How it works: The hook attaches event listeners to the document (or a specified container) and checks if click events occur outside the referenced element. When a click outside is detected, the provided callback is executed. The hook supports both mouse and touch events for mobile compatibility and can be conditionally enabled or disabled.

Basic usage:

import { useClickOutside } from '@commons-dev/react-hooks';
import { useState } from 'react';

function Dropdown() {
  const [isOpen, setIsOpen] = useState(false);
  const ref = useClickOutside(() => {
    setIsOpen(false);
  });

  return (
    <div>
      <button onClick={() => setIsOpen(!isOpen)}>Toggle Dropdown</button>
      {isOpen && (
        <div ref={ref} className="dropdown-menu">
          <div>Menu Item 1</div>
          <div>Menu Item 2</div>
        </div>
      )}
    </div>
  );
}

With options:

import { useClickOutside } from '@commons-dev/react-hooks';
import { useState } from 'react';

function Modal() {
  const [isOpen, setIsOpen] = useState(false);
  const ref = useClickOutside(
    () => {
      setIsOpen(false);
    },
    {
      eventType: 'click', // Use 'click' instead of 'mousedown'
      enabled: isOpen, // Only listen when modal is open
    }
  );

  if (!isOpen) return null;

  return (
    <div className="modal-overlay">
      <div ref={ref} className="modal-content">
        <h2>Modal Title</h2>
        <p>Modal content goes here</p>
      </div>
    </div>
  );
}

With TypeScript generics:

import { useClickOutside } from '@commons-dev/react-hooks';
import { useRef } from 'react';

function ButtonMenu() {
  const buttonRef = useClickOutside<HTMLButtonElement>(() => {
    console.log('Clicked outside button');
  });

  return (
    <button ref={buttonRef} onClick={() => console.log('Button clicked')}>
      Click me or outside me
    </button>
  );
}

API:

function useClickOutside<T extends HTMLElement = HTMLElement>(
  callback: () => void,
  options?: UseClickOutsideOptions
): React.RefObject<T>;

Parameters:

  • callback - Function to call when a click outside the referenced element is detected
  • options - Optional configuration object
    • eventType - The event type to listen for: 'mousedown' (default) or 'click'
    • enabled - Whether the hook is enabled (default: true)

Returns:

  • A ref object (React.RefObject<T>) that should be attached to the element you want to detect clicks outside of

Features:

  • ✅ Mobile support (automatically listens to touch events when using mousedown)
  • ✅ SSR-safe (handles server-side rendering gracefully)
  • ✅ Configurable event type (mousedown or click)
  • ✅ Conditional enabling/disabling without unmounting
  • ✅ Optimized callback handling (doesn't re-attach event listeners when callback changes)
  • ✅ TypeScript generics for element type inference

Notes:

  • The hook uses mousedown by default for better UX (fires before click)
  • When eventType is 'mousedown', the hook also listens to touchstart events for mobile support
  • When eventType is 'click', only click events are listened to (no touch events)
  • The callback is stored in a ref, so it can be updated without re-attaching event listeners
  • The hook safely handles cases where the ref is null or the event target is null
  • Nested elements inside the referenced element are considered "inside" and won't trigger the callback

Use cases:

  • Closing modals when clicking outside
  • Closing dropdown menus
  • Closing popovers or tooltips
  • Dismissing notifications
  • Closing context menus
  • Any UI element that should close when user clicks outside

usePrevious

Stores the previous value of a variable or prop, useful for comparing current and previous values or detecting changes.

How it works: The hook uses a ref to store the previous value and updates it in a useEffect after each render. On the first render, it returns undefined since there is no previous value yet. On subsequent renders, it returns the value from the previous render.

Basic usage:

import { usePrevious } from '@commons-dev/react-hooks';
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);

  return (
    <div>
      <p>Current: {count}</p>
      <p>Previous: {prevCount ?? 'N/A'}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Detecting changes:

import { usePrevious } from '@commons-dev/react-hooks';
import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const prevUserId = usePrevious(userId);

  useEffect(() => {
    if (prevUserId !== undefined && prevUserId !== userId) {
      console.log(`User changed from ${prevUserId} to ${userId}`);
      // Fetch new user data
    }
  }, [userId, prevUserId]);

  return <div>User ID: {userId}</div>;
}

With props:

import { usePrevious } from '@commons-dev/react-hooks';
import { useEffect } from 'react';

function PriceDisplay({ price }) {
  const prevPrice = usePrevious(price);
  const isIncreasing = prevPrice !== undefined && price > prevPrice;

  return (
    <div>
      <p>Price: ${price}</p>
      {prevPrice !== undefined && (
        <p className={isIncreasing ? 'green' : 'red'}>
          {isIncreasing ? '↑' : '↓'} ${Math.abs(price - prevPrice)}
        </p>
      )}
    </div>
  );
}

API:

function usePrevious<T>(value: T): T | undefined;

Parameters:

  • value - The current value to track

Returns:

  • The previous value (or undefined on the first render)

Features:

  • ✅ Works with any value type (primitives, objects, arrays, etc.)
  • ✅ Preserves reference equality for objects
  • ✅ SSR-safe (handles server-side rendering gracefully)
  • ✅ TypeScript generics for type safety

Notes:

  • Returns undefined on the first render since there is no previous value
  • The hook updates the ref after the render completes (in useEffect)
  • For objects, the hook stores the reference, so if the same object is passed multiple times, it will return the same reference
  • Useful for detecting changes, comparing values, or implementing undo/redo functionality

Use cases:

  • Comparing current and previous values
  • Detecting prop changes
  • Tracking state transitions
  • Implementing change indicators (e.g., price changes, score changes)
  • Debugging state updates
  • Calculating deltas or differences
  • Conditional logic based on previous values

useToggle

Manages a boolean state with a convenient toggle function, perfect for toggling UI states like modals, dropdowns, or feature flags.

How it works: The hook uses useState to manage a boolean value and provides a toggle function that flips the value between true and false. It also provides a setValue function for direct value updates. The toggle and setValue functions are memoized with useCallback to maintain stable references across renders.

Basic usage:

import { useToggle } from '@commons-dev/react-hooks';

function Modal() {
  const [isOpen, toggle, setIsOpen] = useToggle(false);

  return (
    <div>
      <button onClick={toggle}>Toggle Modal</button>
      {isOpen && (
        <div className="modal">
          <p>Modal content</p>
          <button onClick={() => setIsOpen(false)}>Close</button>
        </div>
      )}
    </div>
  );
}

With default initial value:

import { useToggle } from '@commons-dev/react-hooks';

function ToggleButton() {
  const [isEnabled, toggle] = useToggle();

  return (
    <button onClick={toggle} disabled={!isEnabled}>
      {isEnabled ? 'Enabled' : 'Disabled'}
    </button>
  );
}

Using setValue directly:

import { useToggle } from '@commons-dev/react-hooks';

function FeatureToggle() {
  const [isActive, toggle, setIsActive] = useToggle(false);

  const enableFeature = () => setIsActive(true);
  const disableFeature = () => setIsActive(false);

  return (
    <div>
      <button onClick={enableFeature}>Enable</button>
      <button onClick={disableFeature}>Disable</button>
      <button onClick={toggle}>Toggle</button>
      <p>Feature is {isActive ? 'active' : 'inactive'}</p>
    </div>
  );
}

API:

function useToggle(
  initialValue?: boolean
): [boolean, () => void, (value: boolean) => void];

Parameters:

  • initialValue - The initial boolean value (defaults to false)

Returns:

  • A tuple [value, toggle, setValue]:
    • value - The current boolean state
    • toggle - A function that toggles the value between true and false
    • setValue - A function to set the value directly

Features:

  • ✅ Simple boolean state management
  • ✅ Convenient toggle function
  • ✅ Direct value setter for explicit control
  • ✅ Memoized callbacks for stable references
  • ✅ TypeScript support with proper types

Notes:

  • The toggle and setValue functions maintain stable references across renders
  • If no initialValue is provided, the hook defaults to false
  • The hook is useful for any boolean state that needs to be toggled frequently

Use cases:

  • Toggling modals, dropdowns, or menus
  • Managing feature flags or settings
  • Controlling visibility of UI elements
  • Switching between two states (on/off, open/closed, enabled/disabled)
  • Any boolean state that benefits from a toggle function

useTimeout

Executes a callback function after a specified delay, with the ability to clear or reset the timeout. Perfect for delayed actions, auto-save functionality, or showing notifications after a delay.

How it works: The hook uses setTimeout to execute a callback after a specified delay. It provides clear and reset functions to manage the timeout. The hook automatically cleans up the timeout on unmount and updates when the callback or delay changes.

Basic usage:

import { useTimeout } from '@commons-dev/react-hooks';

function Notification() {
  const { clear } = useTimeout(() => {
    console.log('This runs after 2 seconds');
  }, 2000);

  return (
    <div>
      <button onClick={clear}>Cancel Notification</button>
    </div>
  );
}

With reset functionality:

import { useTimeout } from '@commons-dev/react-hooks';

function AutoSave() {
  const [hasChanges, setHasChanges] = useState(false);
  const { reset } = useTimeout(() => {
    saveChanges();
    setHasChanges(false);
  }, 5000);

  const handleChange = () => {
    setHasChanges(true);
    reset(); // Restart the timeout
  };

  return (
    <div>
      {hasChanges && <span>Saving in 5 seconds...</span>}
      <input onChange={handleChange} />
    </div>
  );
}

Disabling the timeout:

import { useTimeout } from '@commons-dev/react-hooks';

function ConditionalTimeout({ shouldRun }: { shouldRun: boolean }) {
  useTimeout(() => {
    console.log('This only runs if shouldRun is true');
  }, shouldRun ? 1000 : null);

  return <div>Content</div>;
}

API:

function useTimeout(
  callback: () => void,
  delay: number | null | undefined
): { clear: () => void; reset: () => void };

Parameters:

  • callback - The function to execute after the delay
  • delay - The delay in milliseconds. Use null or undefined to disable the timeout

Returns:

  • An object with:
    • clear - Function to cancel the timeout
    • reset - Function to restart the timeout with the current delay

Features:

  • ✅ Automatic cleanup on unmount
  • ✅ Updates callback reference when callback changes
  • ✅ Updates delay when delay changes
  • ✅ Can be disabled by passing null or undefined as delay
  • ✅ Handles negative delays gracefully (no-op)
  • ✅ Memoized clear and reset functions for stable references

Notes:

  • The timeout is automatically cleared when the component unmounts
  • If delay is null, undefined, or negative, the timeout will not be set
  • The clear and reset functions maintain stable references across renders
  • The callback reference is kept up to date, so you can safely use closures

Use cases:

  • Showing notifications or alerts after a delay
  • Auto-saving form data
  • Implementing auto-logout after inactivity
  • Delaying API calls or side effects
  • Creating delayed actions or animations
  • Implementing retry logic with delays
  • Showing loading states that auto-dismiss

useInterval

Executes a callback function repeatedly at a specified interval, with the ability to clear or reset the interval. Perfect for polling, timers, animations, or any recurring tasks.

How it works: The hook uses setInterval to execute a callback repeatedly at a specified interval. It provides clear and reset functions to manage the interval. The hook automatically cleans up the interval on unmount and updates when the callback or delay changes.

Basic usage:

import { useInterval } from '@commons-dev/react-hooks';

function Timer() {
  const [seconds, setSeconds] = useState(0);
  const { clear } = useInterval(() => {
    setSeconds((prev) => prev + 1);
  }, 1000);

  return (
    <div>
      <p>Timer: {seconds} seconds</p>
      <button onClick={clear}>Stop Timer</button>
    </div>
  );
}

With reset functionality:

import { useInterval } from '@commons-dev/react-hooks';

function PollingComponent() {
  const { reset } = useInterval(() => {
    fetchData();
  }, 5000);

  const handleManualRefresh = () => {
    fetchData();
    reset(); // Restart the interval
  };

  return (
    <div>
      <button onClick={handleManualRefresh}>Refresh Now</button>
    </div>
  );
}

Disabling the interval:

import { useInterval } from '@commons-dev/react-hooks';

function ConditionalPolling({ shouldPoll }: { shouldPoll: boolean }) {
  useInterval(() => {
    fetchData();
  }, shouldPoll ? 5000 : null);

  return <div>Content</div>;
}

API:

function useInterval(
  callback: () => void,
  delay: number | null | undefined
): { clear: () => void; reset: () => void };

Parameters:

  • callback - The function to execute at each interval
  • delay - The interval in milliseconds. Use null or undefined to disable the interval

Returns:

  • An object with:
    • clear - Function to cancel the interval
    • reset - Function to restart the interval with the current delay

Features:

  • ✅ Automatic cleanup on unmount
  • ✅ Updates callback reference when callback changes
  • ✅ Updates delay when delay changes
  • ✅ Can be disabled by passing null or undefined as delay
  • ✅ Handles negative delays gracefully (no-op)
  • ✅ Memoized clear and reset functions for stable references

Notes:

  • The interval is automatically cleared when the component unmounts
  • If delay is null, undefined, or negative, the interval will not be set
  • The clear and reset functions maintain stable references across renders
  • The callback reference is kept up to date, so you can safely use closures

Use cases:

  • Creating timers and counters
  • Polling APIs for updates
  • Implementing auto-refresh functionality
  • Animations and transitions
  • Periodic data synchronization
  • Implementing heartbeat/ping mechanisms
  • Auto-saving at regular intervals
  • Updating UI elements periodically

Tree-Shaking

This package is fully tree-shakeable. You can import hooks individually to minimize bundle size:

// Import from main entry (tree-shakeable by modern bundlers)
import { useDebounce } from '@commons-dev/react-hooks';

// Or import directly (guaranteed tree-shaking)
import { useDebounce } from '@commons-dev/react-hooks/useDebounce';

Requirements

  • React 16.8.0 or higher
  • React DOM 16.8.0 or higher

Contributing

Contributions are welcome! Please follow these strict guidelines when adding a new hook to ensure consistency, quality, and tree-shakability.

Prerequisites

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/hook-name)
  3. Ensure you have the latest dependencies installed (npm install)

Step-by-Step Hook Addition Process

When adding a new hook, you must follow these steps in order:

Step 1: Create the Hook Implementation

Create a new file in src/hooks/ following the naming convention: useHookName.ts (camelCase).

Requirements:

  • ✅ Use named export: export function useHookName(...)
  • ✅ Include comprehensive JSDoc comments with @param, @returns, and @example
  • ✅ No side effects at module level (pure module for tree-shaking)
  • ✅ Use TypeScript generics when appropriate
  • ✅ Follow existing code style (single quotes, semicolons, etc.)
  • ✅ Follow the same pattern as existing hooks (e.g., useDebounce, useThrottle, useLocalStorage)

Template:

import {} from /* React hooks needed */ 'react';

/**
 * Brief description of what the hook does.
 *
 * @param param1 - Description of param1
 * @param param2 - Description of param2 (optional)
 * @returns Description of return value
 *
 * @example
 * ```tsx
 * const result = useHookName(value);
 * ```
 */
export function useHookName<T>(param1: T, param2?: number): ReturnType {
  // Implementation
}

Step 2: Export the Hook

Add the export to src/hooks/index.ts:

export { useHookName } from './useHookName';

Step 3: Add Build Entry Point for Tree-Shaking

Update vite.config.ts to include the new hook in the build entry points:

entry: {
  index: resolve(__dirname, 'src/index.ts'),
  useHookName: resolve(__dirname, 'src/hooks/useHookName.ts'), // Add this line
},

Step 4: Add Package Exports for Tree-Shaking

Update package.json to add subpath exports for tree-shaking. Place this entry before the "./package.json" entry in the exports object:

"./useHookName": {
  "import": {
    "types": "./dist/useHookName.d.ts",
    "default": "./dist/useHookName.esm.js"
  },
  "require": {
    "types": "./dist/useHookName.d.ts",
    "default": "./dist/useHookName.cjs.js"
  }
},

Tree-Shaking Requirements:

  • ✅ Each hook must be a pure module (no side effects)
  • ✅ No module-level code execution
  • ✅ Only export the hook function itself
  • ✅ Dependencies should be imported, not bundled

Step 5: Create Comprehensive Test Cases

Create a test file in __tests__/hooks/useHookName.test.ts:

Requirements:

  • ✅ Test initial state
  • ✅ Test behavior changes
  • ✅ Test edge cases (null, undefined, empty values, etc.)
  • ✅ Test cleanup functions (useEffect return values)
  • ✅ Use renderHook from @testing-library/react
  • ✅ Use act() when testing state updates
  • ✅ Use fake timers if the hook uses setTimeout/setInterval
  • ✅ Aim for high test coverage

Template:

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { useHookName } from '../../src/hooks/useHookName';

describe('useHookName', () => {
  beforeEach(() => {
    // Setup if needed (e.g., vi.useFakeTimers())
  });

  afterEach(() => {
    // Cleanup if needed (e.g., vi.restoreAllMocks())
  });

  it('should return initial value', () => {
    const { result } = renderHook(() => useHookName(initialValue));
    expect(result.current).toBe(expectedValue);
  });

  // Add more test cases covering edge cases and behavior
});

Step 6: Update Documentation

Add comprehensive documentation to README.md in the "Hooks" section. Follow the same pattern as existing hooks:

Requirements:

  • ✅ Brief description of what the hook does
  • ✅ "How it works" explanation
  • ✅ At least one practical code example
  • ✅ TypeScript API signature
  • ✅ Parameters documentation
  • ✅ Returns documentation
  • ✅ Use cases list
  • ✅ Any additional notes or features

Template:

### useHookName

Brief description of what the hook does.

**How it works:** Explanation of the hook's behavior.

**Basic usage:**

```tsx
import { useHookName } from '@commons-dev/react-hooks';

function ExampleComponent() {
  const result = useHookName(value);

  return <div>{result}</div>;
}
```

**API:**

```typescript
function useHookName<T>(param1: T, param2?: number): ReturnType;
```

**Parameters:**

- `param1` - Description
- `param2` - Description (optional)

**Returns:**

- Description of return value

**Use cases:**

- Use case 1
- Use case 2

Step 7: Verify Everything Works

Before submitting your Pull Request, you must run and pass all of these commands:

npm run lint        # Check for linting errors (must pass)
npm test            # Run tests (must pass with good coverage)
npm run build       # Verify build succeeds (must pass)

Verification Checklist:

  • ✅ All linting errors resolved
  • ✅ All tests pass
  • ✅ Build succeeds without errors
  • ✅ Tree-shaking works (verify imports work correctly)
  • ✅ Documentation is complete and follows the pattern
  • ✅ Code follows the existing style guidelines

Code Style Guidelines

  • Imports: Use named imports from 'react', group imports logically
  • Exports: Always use named exports, never default exports
  • Types: Use TypeScript generics for reusable hooks
  • Comments: Include JSDoc comments for all exported functions
  • Formatting: Follow Prettier configuration (single quotes, semicolons, 80 char width)
  • Naming: Use camelCase for hook names, descriptive variable names

Pull Request Process

  1. Ensure all 7 steps above are completed
  2. Commit your changes with a descriptive message (git commit -m 'Add useHookName hook')
  3. Push to your feature branch (git push origin feature/hook-name)
  4. Open a Pull Request with:
    • Clear description of the hook
    • Reference to any related issues
    • Confirmation that all steps were followed
    • Screenshots or examples if applicable

Review Criteria

Your Pull Request will be reviewed based on:

  • ✅ Implementation follows the same pattern as existing hooks
  • ✅ Comprehensive test coverage
  • ✅ Complete and clear documentation
  • ✅ Tree-shaking compatibility verified
  • ✅ Code quality and style consistency
  • ✅ All verification steps passed

Note: Pull Requests that don't follow all 7 steps will be requested for changes before merging.

License

MIT

Repository

https://github.com/commons-dev-open/react-hooks