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

react-hook-lab

v1.14.1

Published

Lightweight, zero-compromise TypeScript React hooks. Offers an extensive toolkit for modern web apps, spanning advanced browser capabilities, robust caching, DOM intersection/resizing, strict timers, and SSR-ready state management.

Downloads

847

Readme

🧪 react-hook-lab

npm version npm downloads License: MIT TypeScript Visitor Count Buy Me A Coffee

A robust, strictly-typed, and lightweight collection of reusable React hooks.

react-hook-lab is designed to solve the most common challenges in React development without bloating your bundle. Whether you need to sync state across browser tabs, deeply diagnose why your components are re-rendering, debounce async API calls, or observe DOM intersections—this library has you covered.

📚 Read the Full API Documentation & Wiki

🌟 Key Features

  • Zero Dependencies: (Except for React itself).
  • Fully Typed: Written entirely in TypeScript with precise generic types.
  • Tree-shakeable: Import exactly what you need; leave the rest behind.
  • Server-Side Rendering (SSR) Safe: Built with Next.js and Remix in mind. Browser APIs gracefully degrade on the server.
  • Comprehensive Test Suite: High test coverage ensuring rock-solid stability in production.

📖 Table of Contents


🚀 Installation

# Using npm
npm install react-hook-lab

# Using yarn
yarn add react-hook-lab

# Using pnpm
pnpm add react-hook-lab

Note: react (v16.8+) is a peer dependency and must be installed in your project.


🧰 Hooks Overview

Async & Timing

| Hook | Description | |------|-------------| | useAsync | Execute a promise-returning function and track its status, value, and error. | | useAsyncDebounce | Debounce an asynchronous callback (perfect for API searches). | | useDebounce | Delay state updates by a given time to avoid rapid renders. | | useThrottle | Limit the rate at which a state can update. | | useInterval | Declarative setInterval that handles cleanup and closure staleness. | | useTimeout | Declarative setTimeout that handles cleanup. | | useTimezone | Safely access the user's local timezone (SSR compatible). |

Browser & DOM

| Hook | Description | |------|-------------| | useTabVisibility | 🚀 Accurately track tab active/hidden/blur state with multi-monitor & mobile BFCache support. | | usePip | 🚀 Portal React components into a separate Document Picture-in-Picture window seamlessly. | | useFileSystem | 🚀 Powerful local-first hook using the File System Access API to read/overwrite native files continuously. | | useFullscreen | Robust, cross-browser hook to make elements fullscreen programmatically. | | useURL | Access and deeply parse the current browser URL dynamically without a router. | | useClipboard | Read and write text to the user's clipboard with a temporary copied state. | | useOnlineStatus | Track the browser's online/offline network status dynamically. | | useDownload | Download JSON objects, Blobs, strings, or fetch remote URLs programmatically. | | useNotifications | Request OS notification permissions and dispatch native alerts. | | useCamera | Request camera access and record video/audio natively. | | useMicrophone | Request microphone access and monitor real-time volume levels. | | useLocation | Access the browser's Geolocation API securely. | | useIdle | Track user inactivity across the browser tab efficiently. | | useClickOutside | Detect clicks outside a specified element (great for dropdowns/modals). | | useElementSize | Track the width and height of an HTML element. | | useIntersectionObserver| Detect visibility of an element on screen (for lazy loading). | | useResizeObserver | React to changes in a DOM element's dimensions. | | useWidth | Track the global window inner width. |

State Management

| Hook | Description | |------|-------------| | useResource | 🚀 Ultimate data fetching hook with SWR caching, polling, and IndexedDB persistence. | | useResourceCompose | 🚀 Compose and combine multiple useResource instances into a single derived reactive state. | | useSharedState | Share state seamlessly across components and browser tabs in real-time. | | useIndexedDB | Powerful, async state management backed by IndexedDB with cross-tab syncing. | | useCookie | Synchronize state with document cookies, securely handling SSR and JSON parsing. | | useLocalStorage | Persist and sync state in localStorage. | | useSessionStorage | Persist and sync state in sessionStorage. | | useDeepClone | Securely deep-clone objects while returning stable references across re-renders to fix broken memoization. | | useDeepMemo | Like useMemo, but compares dependencies by deep value equality instead of reference. | | useBoolean | Manage a boolean state with specific methods (on, off, toggle). | | useCounter | Manage a numeric counter with built-in min/max bounds. | | usePrevious | Store the previous value of a state or prop after a render. | | useToggle | Toggle between two generic values. |

Debugging

| Hook | Description | |------|-------------| | useRenderReason | Deeply diagnose why a component re-rendered (wasted renders, loops, etc). |

🛠️ Utilities

| Utility | Description | |------|-------------| | deepClone | Securely deep-clone objects, arrays, Maps, Sets, Dates. Handles circular references perfectly. | | deepEqual | Robustly compare objects, arrays, Maps, Sets, Dates for value equality. Safe against circular references. |


📚 API Documentation

⏱️ Async & Timing Hooks

useAsync

Manage complex asynchronous data fetching with a clean state object.

import { useAsync } from "react-hook-lab";

function UserProfile({ userId }) {
  const { execute, data, loading, error } = useAsync(
    () => fetch(`/api/users/${userId}`).then((res) => res.json()),
    [userId]
  );

  if (!data && !loading && !error) return <button onClick={execute}>Fetch User</button>;
  if (loading) return <div>Loading spinner...</div>;
  if (error) return <div>Error: {error.message}</div>;
  return <div>Welcome, {data?.name}</div>;
}

useAsyncDebounce

Debounce an asynchronous callback. Useful for preventing spam API calls when a user types in an autocomplete search box. Returns { result, loading, error }.

import { useAsyncDebounce } from "react-hook-lab";

function Search({ query }) {
  const { result, loading, error } = useAsyncDebounce(
    async () => {
      return await api.get(`/search?q=${query}`);
    }, 
    300,
    [query] // Debounces execution until 300ms after the last change to query
  );

  if (loading) return <div>Searching...</div>;
  if (error) return <div>Error searching</div>;
  return <ul>{result?.map(item => <li key={item.id}>{item.title}</li>)}</ul>;
}

useDebounce

Debounce a fast-changing state value. The debounced value will only reflect the latest value after the specified delay has passed. By default, string values are automatically trimmed; pass { trim: false } to strictly preserve raw whitespace.

import { useState } from "react";
import { useDebounce } from "react-hook-lab";

function Input() {
  const [term, setTerm] = useState("");
  // Trimmed by default:
  const debouncedTerm = useDebounce(term, 500);
  // Or preserve exact whitespace without trimming:
  const debouncedRaw = useDebounce(term, 500, { trim: false });

  // 'debouncedTerm' only updates 500ms after the user stops typing
  return <input onChange={(e) => setTerm(e.target.value)} />;
}

useThrottle

Throttle a state value so that it updates at most once every specified number of milliseconds.

import { useState } from "react";
import { useThrottle } from "react-hook-lab";

function ScrollTracker() {
  const [scroll, setScroll] = useState(0);
  const throttledScroll = useThrottle(scroll, 200);
  // throttledScroll will update maximally once every 200ms regardless of how fast you scroll
}

useInterval

A declarative setInterval hook that safely handles cleanup and React's closure staleness issues.

import { useState } from "react";
import { useInterval } from "react-hook-lab";

function Clock() {
  const [time, setTime] = useState(0);
  // Pass 'null' as the delay to pause the interval!
  useInterval(() => setTime((t) => t + 1), 1000);
}

useTimeout

A declarative setTimeout hook.

import { useState } from "react";
import { useTimeout } from "react-hook-lab";

function Notification() {
  const [show, setShow] = useState(true);
  useTimeout(() => setShow(false), 5000);
  
  if (!show) return null;
  return <div>This will disappear in 5 seconds!</div>;
}

useTimezone

Safely access the user's local timezone. Guarantees safe SSR hydration by returning null on the server and resolving in the client.

import { useTimezone } from "react-hook-lab";

function Welcome() {
  const timezone = useTimezone();
  return <div>Your timezone is: {timezone || 'Loading...'}</div>;
}

🌐 Browser & DOM Hooks

useTabVisibility 🚀 (New)

Accurately track whether the user is actively viewing your application. Unlike naive visibility hooks, it handles multi-monitor window blurring, mobile app minimization / BFCache freezing, and prevents hydration false-positives when users open background tabs.

import { useTabVisibility } from "react-hook-lab";

function VideoPlayer() {
  const { isActive, isVisible, isFocused, wasActive, lastActiveAt } = useTabVisibility({
    requireWindowFocus: true, // Requires window focus in addition to document visibility
    onActivate: () => {
      console.log("Tab resumed!");
      videoRef.current?.play();
    },
    onDeactivate: () => {
      console.log("Tab inactive/blurred");
      videoRef.current?.pause();
    },
  });

  return (
    <div>
      <p>Status: {isActive ? "🟢 Active & Focused" : "🔴 Inactive"}</p>
      <p>Visible on screen: {isVisible ? "Yes" : "No"}</p>
      <p>Window has focus: {isFocused ? "Yes" : "No"}</p>
    </div>
  );
}

usePip 🚀 (New)

Portal React components into an always-on-top Document Picture-in-Picture window. It handles the API lifecycle, seamlessly copies your stylesheets so the PIP window looks identical to your main app, and cleans up cleanly on unmount.

import { usePip } from "react-hook-lab";

function VideoPIP() {
  const { isSupported, isOpen, openPip, closePip, Pip } = usePip();

  if (!isSupported) return <p>Picture-in-Picture not supported!</p>;

  return (
    <div>
      <button onClick={() => isOpen ? closePip() : openPip()}>
        {isOpen ? 'Close PIP' : 'Open PIP'}
      </button>
      
      {/* Renders children into the PIP window while copying main app's styles */}
      <Pip width={400} height={300}>
        <div className="my-pip-content">
          <h2>I am inside the PIP window! 🚀</h2>
        </div>
      </Pip>
    </div>
  );
}

useFileSystem 🚀 (New)

Uses the modern File System Access API to allow users to grant permission to a specific file on their hard drive. The hook provides methods to continuously read from or write to that file without prompting them to re-download it every time. Perfect for local-first apps like markdown editors!

import { useFileSystem } from "react-hook-lab";

function LocalTextEditor() {
  const { isSupported, file, open, save, saveAs } = useFileSystem({ 
    accept: { 'text/plain': ['.txt'] } 
  });

  if (!isSupported) return <div>Browser not supported</div>;

  return (
    <div>
      <button onClick={() => open()}>Open Local File</button>
      
      {/* If a file is open, save() overwrites it. If no file is open, save() delegates to saveAs()! */}
      <button onClick={() => save("New text content!")}>
        Save
      </button>

      <button onClick={() => saveAs("New text content!", { suggestedName: 'copy.txt' })}>
        Save As...
      </button>

      <p>Currently editing: {file ? file.name : "Untitled"}</p>
    </div>
  );
}

useFullscreen

Make any specific DOM element or the entire document fullscreen programmatically across all modern browsers. Guaranteed SSR safe.

import { useFullscreen } from "react-hook-lab";

function VideoPlayer() {
  const { ref, isFullscreen, toggle, error } = useFullscreen<HTMLVideoElement>();

  return (
    <div>
      {error && <p style={{ color: "red" }}>{error.message}</p>}
      <video ref={ref} src="movie.mp4" controls />
      <button onClick={toggle}>
        {isFullscreen ? "Exit Fullscreen" : "Enter Fullscreen"}
      </button>
    </div>
  );
}

useURL

A highly optimized hook that provides complete, deeply-parsed information about the current browser URL. It automatically reacts to programmatic navigation (pushState), back/forward buttons, and hash changes without needing an external routing library. Fully SSR safe.

import { useURL } from "react-hook-lab";

function Breadcrumbs() {
  const { pathname, breadcrumbs, query, changed } = useURL();

  return (
    <div>
      <p>Current Path: {pathname}</p>
      <nav>
        {breadcrumbs.map((crumb) => (
          <a key={crumb.path} href={crumb.path}>
            {crumb.name}
          </a>
        ))}
      </nav>
      {changed && <p>URL just changed! Query page: {query.page}</p>}
    </div>
  );
}

useClipboard

Read and write text to the user's clipboard with a temporary copied state.

import { useClipboard } from "react-hook-lab";

function CopyButton() {
  const { copy, copied, error } = useClipboard(2000); // 'copied' resets after 2s
  return <button onClick={() => copy("Text to copy!")}>{copied ? "Copied!" : "Copy"}</button>;
}

useOnlineStatus

Detect whether the browser has an active network connection dynamically.

import { useOnlineStatus } from "react-hook-lab";

function Status() {
  const isOnline = useOnlineStatus();
  return <div>{isOnline ? "✅ Online" : "❌ Offline (Check your connection)"}</div>;
}

useDownload

Download JSON objects, raw strings, Blobs, or fetch remote URLs with a clean asynchronous API.

import { useDownload } from "react-hook-lab";

function ExportData() {
  const { download, status, error } = useDownload();

  const handleExport = () => {
    // Easily download an object as a .json file
    download({ user: "Admin", data: [1, 2, 3] }, "export.json");
    
    // Or fetch a remote file automatically
    // download("https://picsum.photos/200", "image.jpg");
  };

  return (
    <div>
      <button onClick={handleExport} disabled={status === "downloading"}>
        {status === "downloading" ? "Downloading..." : "Export"}
      </button>
      {error && <p style={{ color: "red" }}>{error}</p>}
    </div>
  );
}

useNotifications

Request OS notification permissions and dispatch native system alerts effortlessly.

import { useNotifications } from "react-hook-lab";

function Alerts() {
  // Pass { autoRequest: false } to wait for a user gesture before prompting
  const { permission, requestPermission, sendNotification } = useNotifications();

  const notify = () => {
    if (permission !== "granted") {
      requestPermission();
    } else {
      sendNotification("Download Complete", { body: "Your export.json is ready." });
    }
  };

  return <button onClick={notify}>Enable Alerts</button>;
}

useCamera

Request camera permissions and easily record video or take snapshots.

import { useCamera } from "react-hook-lab";

function Webcam() {
  const { videoRef, requestCamera, status, stopCamera } = useCamera();
  
  return (
    <div>
      <button onClick={requestCamera}>Start Camera</button>
      <video ref={videoRef} autoPlay playsInline muted />
    </div>
  );
}

useMicrophone

Request microphone permissions, monitor audio levels in real-time, and record audio.

import { useMicrophone } from "react-hook-lab";

function AudioMonitor() {
  const { requestMicrophone, audioLevel, status } = useMicrophone();
  
  return (
    <div>
      <button onClick={requestMicrophone}>Start Mic</button>
      <div>Volume: {audioLevel}%</div>
    </div>
  );
}

useLocation

Securely access the user's latitude and longitude via the Geolocation API.

import { useLocation } from "react-hook-lab";

function Map() {
  const { location, retry, status } = useLocation();
  
  if (status === "idle") return <button onClick={retry}>Get Location</button>;
  if (status === "prompting") return <div>Locating...</div>;
  
  return <div>Lat: {location?.lat}, Lng: {location?.lng}</div>;
}

useIdle

Detect when a user has stopped interacting with your page.

import { useIdle } from "react-hook-lab";

function InactivityWarning() {
  const isIdle = useIdle(60000); // 1 minute
  return isIdle ? <div>Are you still there?</div> : null;
}

useClickOutside

The standard solution for closing menus, modals, and dropdowns when a user clicks outside of them.

import { useRef, useState } from "react";
import { useClickOutside } from "react-hook-lab";

function Dropdown() {
  const [isOpen, setIsOpen] = useState(false);
  const ref = useRef<HTMLDivElement>(null);

  useClickOutside(ref, () => setIsOpen(false));

  return (
    <div ref={ref}>
      <button onClick={() => setIsOpen(true)}>Menu</button>
      {isOpen && <div className="menu-items">...</div>}
    </div>
  );
}

useElementSize

Track the height and width of a specific DOM element in real-time.

import { useRef } from "react";
import { useElementSize } from "react-hook-lab";

function Box() {
  const ref = useRef<HTMLDivElement>(null);
  const { width, height } = useElementSize(ref);

  return <div ref={ref}>Size: {width}px x {height}px</div>;
}

useIntersectionObserver

Track whether a DOM element is currently visible in the viewport. Ideal for lazy loading images or infinite scrolling.

import { useRef } from "react";
import { useIntersectionObserver } from "react-hook-lab";

function LazyImage({ src }) {
  const ref = useRef<HTMLImageElement>(null);
  const entry = useIntersectionObserver(ref, { threshold: 0.1 });
  const isVisible = !!entry?.isIntersecting;

  return <img ref={ref} src={isVisible ? src : "placeholder.jpg"} alt="Lazy loaded" />;
}

useResizeObserver

React to changes in a DOM element's dimensions using the native ResizeObserver API.

import { useRef } from "react";
import { useResizeObserver } from "react-hook-lab";

function Resizable() {
  const ref = useRef<HTMLDivElement>(null);
  const entry = useResizeObserver(ref);

  return <div ref={ref}>Width: {entry?.contentRect.width}</div>;
}

useWidth

Track the global inner window width dynamically.

import { useWidth } from "react-hook-lab";

function View() {
  const width = useWidth();
  return <div>{width < 768 ? "Mobile View" : "Desktop View"}</div>;
}

💾 State Management Hooks

useResource 🚀 (New)

The ultimate hook for data fetching, server-state management, and optimistic mutations. It combines SWR (Stale-While-Revalidate) caching strategies with our useSharedState and useIndexedDB primitives, giving you cross-tab synchronization and offline disk persistence automatically!

import { useResource } from "react-hook-lab";

function UserProfile({ userId }) {
  const { data, loading, error, mutate } = useResource({
    key: `user:${userId}`,
    fetcher: (signal) => fetch(`/api/users/${userId}`, { signal }).then(res => res.json()),
    cache: "indexeddb",       // Persist to disk for offline support
    persist: { store: "users" },
    staleTime: 5 * 60 * 1000, // Serve from cache without fetching for 5 minutes
  });

  if (loading && !data) return <div>Loading...</div>;
  if (error) return <div>Error!</div>;
  
  return (
    <div>
      <h1>{data.name}</h1>
      <button onClick={() => mutate(prev => ({ ...prev, name: "New Name" }))}>
        Optimistic Update
      </button>
    </div>
  );
}

useResourceCompose 🚀 (New)

Combine and merge multiple independent useResource instances into a single derived reactive state. Automatically updates whenever any dependency emits new data, with custom equality checks to prevent unnecessary re-renders.

import { useResource, useResourceCompose } from "react-hook-lab";

function UserDashboard({ userId }) {
  const user = useResource({
    key: `user:${userId}`,
    fetcher: () => fetch(`/api/users/${userId}`).then(res => res.json()),
  });

  const posts = useResource({
    key: `posts:${userId}`,
    fetcher: () => fetch(`/api/users/${userId}/posts`).then(res => res.json()),
  });

  const dashboard = useResourceCompose({
    key: `dashboard:${userId}`,
    deps: { user, posts },
    selector: ({ user, posts }) => ({
      author: user?.name ?? "Anonymous",
      totalPosts: posts?.length ?? 0,
      recentPost: posts?.[0]?.title ?? "No posts yet",
    }),
  });

  if (dashboard.loading && !dashboard.data) return <div>Loading dashboard...</div>;

  return (
    <div>
      <h2>{dashboard.data?.author}'s Overview</h2>
      <p>Total Posts: {dashboard.data?.totalPosts}</p>
      <p>Latest: {dashboard.data?.recentPost}</p>
    </div>
  );
}

useSharedState 🚀 (Advanced)

A powerful hook that replaces complex global state managers (like Redux or Zustand) for simple values, while also syncing the state across multiple browser tabs in real-time using BroadcastChannel.

import { useSharedState } from "react-hook-lab";

// Component A (Maybe in a completely different browser tab!)
function ThemeToggle() {
  const [theme, setTheme] = useSharedState("global-theme", "light");
  return <button onClick={() => setTheme("dark")}>Set Dark</button>;
}

// Component B
function App() {
  const [theme] = useSharedState("global-theme", "light");
  return <div className={`app ${theme}`}>...</div>;
}

useIndexedDB 🚀

A powerful, asynchronous state management hook backed by the browser's IndexedDB. It stores any data type (including Dates, Blobs, and Sets) without serialization, and securely syncs state across all browser tabs in real-time.

Initialization: (Call once globally)

import { createIndexedDB } from "react-hook-lab";

createIndexedDB({
  dbName: "my-app-storage",
  version: 1,
  stores: ["settings", "cache"]
});

Usage in components:

import { useIndexedDB } from "react-hook-lab";

function ThemeToggle() {
  const [theme, setTheme, { status, remove }] = useIndexedDB("settings", "theme", "light");
  
  if (status === "loading") return <div>Loading...</div>;

  return (
    <div>
      <p>Current theme: {theme}</p>
      <button onClick={() => setTheme("dark")}>Set Dark</button>
      <button onClick={() => remove()}>Reset</button>
    </div>
  );
}

useCookie 🚀 (New)

Synchronize state with browser cookies. Strictly string-based and completely hydration-safe for Server-Side Rendering (SSR). It cleanly separates initial renders from client reconciliation and supports all native cookie attributes.

import { useCookie } from "react-hook-lab";

function CookieBanner() {
  // Pass an options object with your initial SSR/fallback value
  const [consentStr, setCookie, deleteCookie] = useCookie("cookie_consent", { 
    initialValue: "false" 
  });
  
  if (consentStr === "true") return null;

  return (
    <div className="banner">
      <p>We use cookies to improve your experience.</p>
      <button 
        onClick={() => setCookie("true", { 
          days: 365, 
          secure: true, 
          sameSite: "strict" 
        })}
      >
        Accept
      </button>
      <button onClick={() => deleteCookie()}>Revoke Consent</button>
    </div>
  );
}

useLocalStorage

Synchronize state with localStorage. Safely handles JSON parsing and server-side rendering hydration.

import { useLocalStorage } from "react-hook-lab";

function Settings() {
  const [config, setConfig] = useLocalStorage("user-settings", { notifications: true });
  
  return (
    <button onClick={() => setConfig({ notifications: !config.notifications })}>
      Toggle Alerts
    </button>
  );
}

useSessionStorage

Synchronize state with sessionStorage. Survives page reloads but clears when the tab is closed.

import { useSessionStorage } from "react-hook-lab";

function FormCache() {
  const [name, setName] = useSessionStorage("draft-name", "");
  return <input value={name} onChange={(e) => setName(e.target.value)} />;
}

useDeepClone

As we all know, there wasn't a simple built-in way to clone data deeply in JavaScript without performance issues or missing features. This hook helps developers to clone data at the root level efficiently.

It works for every data type available: both primitives (strings, numbers, booleans) and non-primitives (deeply nested objects, arrays, Maps, Sets, Dates, and TypedArrays). It even perfectly handles complex Circular References!

Crucially for React, it guarantees a stable memory reference across renders if the input hasn't changed, completely fixing broken memoization.

import { useDeepClone } from "react-hook-lab";

function HeavyComponent({ complexConfig }) {
  // If the parent passes a brand new object literal, useDeepClone intercepts it.
  // It deeply clones it, but if the content is identical to last render, 
  // it returns the exact same cached reference!
  const safeConfig = useDeepClone(complexConfig);

  useEffect(() => {
    // This effect is now perfectly stable and won't infinite loop
  }, [safeConfig]);
}

useDeepMemo

Like React's useMemo, but it compares the dependencies array using deep value equality instead of strict reference equality (===).

This is incredibly useful when a dependency is an object or array literal that is recreated on every render but contains the exact same data.

import { useDeepMemo } from "react-hook-lab";

function DataProcessor({ filterOptions }) {
  // Even if filterOptions is a new object reference every render,
  // processedData will ONLY be recomputed if the actual contents change!
  const processedData = useDeepMemo(() => {
    return expensiveProcessing(filterOptions);
  }, [filterOptions]);

  return <div>{processedData.length} results</div>;
}

useBoolean

Easily manage boolean state with dedicated on, off, and toggle methods.

import { useBoolean } from "react-hook-lab";

function Modal() {
  const { value: isOpen, setTrue: open, setFalse: close, toggle } = useBoolean(false);
  return <button onClick={toggle}>Toggle Modal</button>;
}

useCounter

Manage numeric state with built-in min and max bounds.

import { useCounter } from "react-hook-lab";

function Cart() {
  const { count, increment, decrement, reset, set } = useCounter(1, { min: 1, max: 10 });
  return <button onClick={increment}>Add: {count}</button>;
}

usePrevious

Access the previous value of a state or prop after a render.

import { useState } from "react";
import { usePrevious } from "react-hook-lab";

function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);
  return <div>Now: {count}, Before: {prevCount}</div>;
}

useToggle

Toggle between two generic values of any type.

import { useToggle } from "react-hook-lab";

function Mode() {
  const { value: mode, toggle: toggleMode } = useToggle("light", "dark");
  return <button onClick={toggleMode}>Current: {mode}</button>;
}

🐛 Debugging Hooks

useRenderReason 🔬 (Advanced)

Stop guessing why your React component is lagging or looping. Drop this hook into any component to instantly log exactly what prop, state, or Context changed, or if a parent triggered a "wasted render".

Note: This hook is completely zero-overhead in production! It automatically disables itself when process.env.NODE_ENV === 'production' to ensure your app remains lightning fast.

import { useRenderReason } from "react-hook-lab";

function HeavyChart({ data, options, onPointClick }) {
  // Pass a label and an object containing the props you want to track
  useRenderReason("HeavyChart", { data, options, onPointClick });

  return <CanvasChart data={data} />;
}

Console Output Examples:

  • 🟢 [useRenderReason] HeavyChart — render #2 (14ms since last)
  • 🔴 data: object reference changed, value unchanged. → Same content, new reference. Consider useMemo.
  • 🔵 Context(ThemeContext): object reference changed, value unchanged. → Same content, new reference.
  • 🟡 No tracked props/state changed — likely a wasted render triggered by a parent. Consider React.memo.
  • 🚨 WARNING: Rendered more than expected in a short window — possible re-render loop or missing memoization.

🛠️ Utilities

deepClone

A highly-optimized, secure deep cloning utility that works for all data types (including nested objects, arrays, Maps, Sets, Dates, and TypedArrays) while flawlessly handling circular references.

It is completely decoupled from React and can be used in standard JavaScript/Node.js environments, making it perfect for your backend or pure logic files.

Note: Utilities are imported from the /utils path to keep the main bundle extremely lightweight.

import { deepClone } from "react-hook-lab/utils";

const original = {
  user: { name: "Alice" },
  date: new Date(),
  config: new Map([["theme", "dark"]])
};

// Even handles complex circular references!
original.self = original; 

const cloned = deepClone(original);

console.log(cloned !== original); // true
console.log(cloned.user !== original.user); // true
console.log(cloned.self === cloned); // true (reference structure preserved)

deepEqual

A highly-optimized, secure deep equality utility that compares primitives, nested objects, arrays, Maps, Sets, Dates, and TypedArrays by value instead of strict reference equality. It handles circular references safely without crashing.

import { deepEqual } from "react-hook-lab/utils";

const obj1 = { name: "Alice", nested: { a: 1 } };
const obj2 = { name: "Alice", nested: { a: 1 } };

console.log(obj1 === obj2); // false
console.log(deepEqual(obj1, obj2)); // true

const map1 = new Map([["key", "value"]]);
const map2 = new Map([["key", "value"]]);
console.log(deepEqual(map1, map2)); // true

🌍 Browser Compatibility

  • useSharedState: Relies on BroadcastChannel. Supported in all modern browsers (Chrome 54+, Firefox 38+, Safari 15.4+).
  • useClipboard: Relies on navigator.clipboard. Supported in modern browsers over HTTPS.
  • useIntersectionObserver: Relies on IntersectionObserver. Supported in all modern browsers.
  • useResizeObserver: Relies on ResizeObserver. Supported in all modern browsers.

Polyfills may be required for legacy environments (like Internet Explorer 11).


🤝 Contributing

We welcome contributions! Please follow these steps:

  1. Fork the repository.
  2. Create your feature branch (git checkout -b feature/amazing-hook).
  3. Commit your changes (git commit -m 'feat: add amazing-hook').
  4. Ensure tests pass (npm run test).
  5. Push to the branch (git push origin feature/amazing-hook).
  6. Open a Pull Request.

Built with ❤️ by Saurav-TB-Pandey.

Connect with me