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

special-hooks

v2.0.1

Published

A lightweight, tree-shakeable, zero-dependency collection of essential React hooks written in TypeScript.

Readme

special-hooks

A lightweight, tree-shakeable, zero-dependency collection of essential React hooks, written in TypeScript.

  • Zero runtime dependencies - only react as a peer dependency.
  • Tree-shakeable - import 50 hooks or one; your bundle only ships what you use (useToggle adds ~300 B).
  • TypeScript-first - every hook is fully typed with exported types.
  • SSR-safe - browser APIs are guarded, so it works with Next.js, Remix, etc.
  • Dual ESM + CJS builds with per-hook subpath exports.

Installation

npm install special-hooks
# or
yarn add special-hooks
# or
pnpm add special-hooks

react and react-dom (v18+) are peer dependencies.

Usage

Import from the package root:

import { useToggle, useDebounce, useLocalStorage } from "special-hooks";

Or import a single hook via its subpath (handy for the smallest possible footprint):

import useToggle from "special-hooks/use-toggle";

Hooks

State

useCounter

Numeric counter with clamping, custom step, set and reset.

const { count, increment, decrement, set, reset } = useCounter(0, {
  min: 0,
  max: 10,
  step: 2,
});

useToggle

Boolean state with stable helper actions.

const [isOpen, { toggle, setTrue, setFalse, set }] = useToggle(false);

usePrevious

Returns the value from the previous render.

const previousCount = usePrevious(count);

useStep

Navigate a multi-step flow (wizards, onboarding).

const [step, { goToNextStep, goToPrevStep, reset, canGoToNextStep }] = useStep(4);

useDebounce

Debounce a changing value.

const debouncedSearch = useDebounce(search, 300);

useThrottle

Throttle a changing value.

const throttledScroll = useThrottle(scrollY, 200);

useDebouncedCallback

Debounce a function, with cancel and flush.

const save = useDebouncedCallback((value: string) => api.save(value), 500);
save("draft"); // runs 500ms after the last call

Storage

useLocalStorage

Persist state to localStorage, with cross-tab sync and a remove action.

const { value, setValue, remove } = useLocalStorage("theme", "light");
setValue((prev) => (prev === "light" ? "dark" : "light"));

useSessionStorage

Same API as useLocalStorage, backed by sessionStorage.

const { value, setValue, remove } = useSessionStorage("draft", "");

Data

useFetch

Fetch JSON with abort-on-unmount, refetch, and request options.

const { data, loading, error, refetch } = useFetch<User[]>("/api/users", {
  enabled: true,
});

useAsync

Run an async function and track its lifecycle.

const { status, value, error, loading, execute } = useAsync(fetchProfile);

useTable

Client-side pagination and sorting for arrays of data.

const {
  paginatedData,
  currentPage,
  totalPages,
  nextPage,
  prevPage,
  toggleSort,
  sortBy,
  sortDirection,
} = useTable({ data: rows, initialPageSize: 10 });

useValidateEmail

Synchronous email validity check.

const isValid = useValidateEmail(email);

UI / DOM

useClickOutside

Run a handler when the user clicks/taps outside the referenced element.

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

useHover

Track hover state of an element.

const [ref, isHovered] = useHover<HTMLButtonElement>();

useMediaQuery

Reactively match a media query (SSR-safe).

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

useWindowSize

Reactive { width, height } of the window.

const { width, height } = useWindowSize();

useElementSize / useResizeObserver

Track an element's size via ResizeObserver.

const [ref, { width, height }] = useElementSize<HTMLDivElement>();

useIntersectionObserver

Observe element visibility for lazy-loading, infinite scroll, or reveal animations.

const { ref, isIntersecting } = useIntersectionObserver({ threshold: 0.5 });

useScrollLock

Lock and unlock body scroll (modals, drawers) without layout shift.

const { lock, unlock } = useScrollLock();

useAutoScroll

Keep a container pinned to the bottom as content arrives (chat-style), but only while the user is already at the bottom.

const { ref, isPinned, scrollToBottom } = useAutoScroll<HTMLDivElement>();

useHorizontalOverflow / useVerticalOverflow

Detect whether a container's content overflows.

const ref = useRef<HTMLDivElement>(null);
const hasOverflow = useHorizontalOverflow(ref);

useTheme

Light/dark theme with system-preference detection, persistence, and DOM application.

const { theme, setTheme, toggleTheme } = useTheme({ attribute: "data-theme" });

useDocumentTitle

Set document.title, optionally restoring it on unmount.

useDocumentTitle("Dashboard");

useCopyToClipboard

Copy text with success feedback and error reporting.

const { copied, error, copy } = useCopyToClipboard();
<button onClick={() => copy("hello")}>{copied ? "Copied!" : "Copy"}</button>;

Lifecycle

useInterval

Declarative setInterval; pass null to pause.

useInterval(() => tick(), isRunning ? 1000 : null);

useTimeout

Declarative setTimeout with reset/clear.

const { reset, clear } = useTimeout(() => setVisible(false), 3000);

useUpdateEffect

Like useEffect, but skips the initial mount.

useUpdateEffect(() => {
  console.log("value changed", value);
}, [value]);

useIsMounted

Returns a function that reports whether the component is still mounted.

const isMounted = useIsMounted();
if (isMounted()) setState(data);

useEventListener

Type-safe event listener for window, document, or an element ref.

useEventListener("keydown", (e) => {
  if (e.key === "Escape") close();
});

Network

useNetworkState / useOnline

Track connectivity and (where supported) connection details.

const { online, effectiveType, downlink } = useNetworkState();
const isOnline = useOnline();

Migration to 2.0

2.0 is a ground-up rewrite. Notable breaking changes:

  • Removed hooks: useRandomEmails, useRandomColors, useDateTime, and useCardType were removed (low utility / buggy).
  • useCopyToClipboard now returns { copied, error, copy } (was [copied, handleCopy]) and no longer depends on copy-to-clipboard.
  • useLocalStorage / useSessionStorage now return { value, setValue, remove } (was [value, setValue]).
  • useTheme now returns { theme, setTheme, toggleTheme } (added setTheme) and accepts an options object.
  • useFetch now returns { data, loading, error, refetch } and accepts request options.
  • The package is now zero-dependency, tree-shakeable, and ships dual ESM/CJS with per-hook subpath exports.

Development

npm install
npm test          # run the test suite
npm run typecheck # type-check without emitting
npm run build     # build ESM + CJS + type declarations
npm run size      # check per-hook bundle sizes

Contributing

Contributions are welcome. Each hook lives in its own folder under src/hooks/<category>/<hook>/ with a co-located test. Add new hooks there, export them from src/index.ts, and include a subpath entry in package.json's exports.

License

Apache-2.0