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-granth

v1.1.1

Published

A collection of custom React hooks for efficient state management and UI logic.

Readme

react-hook-granth

A collection of custom React hooks for state management, browser APIs, and common UI logic. Written in TypeScript, ships ESM and CommonJS builds, and is tree-shakeable so you only bundle the hooks you import.

npm version total downloads license

Features

  • 20 focused, composable hooks with full TypeScript types.
  • Zero runtime dependencies; react and react-dom are peer dependencies.
  • Dual ESM/CJS output with per-hook ES modules and "sideEffects": false for effective tree-shaking.
  • SSR-safe: hooks that touch window, document, or navigator guard against server rendering.

Requirements

  • React >= 16.8.0 (hooks support).

Installation

npm install react-hook-granth
# or
yarn add react-hook-granth
# or
pnpm add react-hook-granth

Usage

Import named hooks from the package root. Only the hooks you import are included in your bundle.

import { useCounter, useLocalStorage, useDebounce } from 'react-hook-granth';

function Example() {
  const { count, increment, decrement, reset } = useCounter(0);
  const [name, setName] = useLocalStorage('name', '');
  const { debouncedValue } = useDebounce(name, 300);

  return (
    <div>
      <p>{count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>reset</button>

      <input value={name} onChange={(e) => setName(e.target.value)} />
      <p>Debounced: {debouncedValue}</p>
    </div>
  );
}

Both module systems are supported:

import { useCounter } from 'react-hook-granth'; // ESM
const { useCounter } = require('react-hook-granth'); // CommonJS

API

All hooks are named exports. Public option/return types are exported alongside each hook.

State management

useCounter(initialValue?: number | null): UseCounterReturn

Manages a numeric counter. null/undefined are treated as 0. Returns { count, increment, decrement, reset } with stable callbacks. reset returns to the initial value.

const { count, increment, decrement, reset } = useCounter(10);

useLocalStorage<T>(key, initialValue): [T, Dispatch<SetStateAction<T>>]

Syncs state with localStorage. The setter accepts a value or an updater function, like useState. SSR-safe, and read/write failures (including invalid stored JSON) fall back to initialValue without throwing.

const [theme, setTheme] = useLocalStorage('theme', 'light');
setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));

useSessionStorage<T>(key, initialValue): [T, Dispatch<SetStateAction<T>>]

Same contract as useLocalStorage, backed by sessionStorage.

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

Returns the value from the previous render; undefined on the first render.

const previousCount = usePrevious(count);

Performance & effects

useDebounce<T>(value, delay?, options?): UseDebounceReturn<T>

Debounces a changing value. delay defaults to 300 ms. Returns { debouncedValue, cancel, flush, isPending }.

UseDebounceOptions<T>:

| Option | Type | Default | Description | | ------------ | -------------------- | ------- | --------------------------------------------- | | leading | boolean | false | Commit on the leading edge. | | trailing | boolean | true | Commit on the trailing edge. | | maxWait | number | — | Force a commit after this many ms. | | onDebounce | (value: T) => void | — | Called when the debounced value is committed. | | onCancel | () => void | — | Called when a pending debounce is cancelled. |

const { debouncedValue, isPending, cancel, flush } = useDebounce(query, 300);

useThrottle<T extends (...args) => any>(callback, delay): (...args) => void

Returns a throttled callback that runs at most once per delay ms. The leading call fires immediately; a trailing call fires at the end of the window with the most recent arguments, so the final call in a burst is not dropped. The pending trailing call is cleared on unmount.

const onScroll = useThrottle(handleScroll, 200);

useTimeout(callback, delay): UseTimeoutReturn

Runs callback after delay ms and restarts when delay changes. Returns { clear, reset } to cancel or restart the timer. Cleans up on unmount.

const { clear, reset } = useTimeout(() => save(), 1000);

useIdle(timeout?: number): boolean

Returns true after timeout ms (default 3000) with no user activity. Resets on mousemove, mousedown, keydown, scroll, and touchstart. SSR-safe.

const isIdle = useIdle(5000);

User interaction

useClickOutside<T extends HTMLElement>(handler): RefObject<T | null>

Returns a ref to attach to an element. handler is called on mousedown/touchstart outside that element. The latest handler is always used without re-attaching listeners.

const ref = useClickOutside<HTMLDivElement>(() => setOpen(false));
return <div ref={ref} />;

useCopyToClipboard(options?): UseCopyToClipboardReturn

Copies text via the async Clipboard API. Returns { isCopied, copy, reset }, where copy(text) resolves to a boolean. isCopied auto-resets after resetTime ms.

UseCopyToClipboardOptions:

| Option | Type | Default | Description | | ----------- | ------------------------ | ------- | ------------------------------- | | resetTime | number | 2000 | Delay before isCopied resets. | | onSuccess | () => void | — | Called after a successful copy. | | onError | (error: Error) => void | — | Called when copying fails. |

const { isCopied, copy } = useCopyToClipboard({ resetTime: 1500 });
await copy('hello');

Browser APIs

useWindowSize(): WindowSize

Returns { width, height } and updates on resize. Returns { width: 0, height: 0 } during SSR.

useMediaQuery(query: string): boolean

Returns whether a CSS media query currently matches, updating on change. Returns false during SSR.

const isDesktop = useMediaQuery('(min-width: 1024px)');

useOnlineStatus(): boolean

Returns the browser online status, updating on online/offline. Assumes online during SSR.

useNetworkSpeed(): NetworkSpeed

Returns { connectionType, downlink } from the Network Information API, updating on the connection's change event and on online/offline. Falls back to { connectionType: 'unknown', downlink: 0 } when the API is unavailable.

useScrollPosition(): ScrollPosition

Returns { x, y } window scroll offsets, updating on scroll. Returns { x: 0, y: 0 } during SSR.

useScrollIntoView<T extends HTMLElement>(ref, triggers?, delay?, options?, onScrollComplete?): UseScrollIntoViewReturn

Scrolls ref into view when any truthy value in triggers changes, or manually via the returned scrollToElement. delay defaults to 100 ms; options are standard scrollIntoView options. Returns { hasScrolled, error, scrollToElement }.

const ref = useRef<HTMLDivElement>(null);
const { scrollToElement, hasScrolled, error } = useScrollIntoView(ref, [open]);

useIntersectionObserver<T extends HTMLElement>(options?): UseIntersectionObserverReturn<T>

Observes element visibility with IntersectionObserver. Returns { ref, isIntersecting, entry }; attach ref to the target element. SSR-safe.

UseIntersectionObserverOptions extends IntersectionObserverInit with:

| Option | Type | Default | Description | | ------------------- | --------- | ------- | ----------------------------------------------- | | freezeOnceVisible | boolean | false | Stop observing after the element first appears. |

const { ref, isIntersecting } = useIntersectionObserver({ threshold: 0.5 });
return <div ref={ref}>{isIntersecting ? 'visible' : 'hidden'}</div>;

Utility

useDocumentTitle(title: string): void

Sets document.title whenever title changes.

useMeasure<T extends HTMLElement>(): [RefObject<T | null>, Bounds]

Returns a ref and the element's { width, height }. Re-measures on window resize and, where supported, on element resize via ResizeObserver.

const [ref, { width, height }] = useMeasure<HTMLDivElement>();
return <div ref={ref} />;

Package format

| Field | Value | | ------------- | ------------------- | | main (CJS) | dist/index.cjs | | module(ESM) | dist/esm/index.js | | types | dist/index.d.ts | | sideEffects | false |

The ESM build preserves per-hook module boundaries (dist/esm/hooks/*.js), so bundlers drop unused hooks entirely.

Development

npm install
npm run build        # rollup -> dist (ESM per-module, CJS bundle, .d.ts)
npm run test:run     # run the test suite once (Vitest + jsdom)
npm run type-check   # tsc --noEmit on source
npm run lint         # ESLint
npm run check        # type-check + type-check:test + lint + format:check + tests

Contributing

Issues and pull requests are welcome. Please run npm run check before opening a PR.

License

MIT © Yuvraj Karna