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

@zap-studio/react-hooks

v1.1.0

Published

Small, focused, tree-shakeable React hooks — one hook, one subpath, no forced bundle.

Downloads

105

Readme

@zap-studio/react-hooks

Small, focused React hooks. Each one ships as its own subpath export, so importing one never pulls in unrelated hooks.

Full documentation: zapstudio.dev/react-hooks

Motivation

Reaching a browser API from React usually means hand-writing a useEffect — a resize listener, a matchMedia subscription, an IntersectionObserver — and each one carries the same two risks: forget the cleanup and you get a leaked listener that keeps firing after unmount, or forget to guard window/document/navigator and the same code throws on the server or hydrates to a value the client's first render did not have.

@zap-studio/react-hooks wraps these APIs — IntersectionObserver, ResizeObserver, the Battery/Geolocation/Share/Wake Lock APIs, matchMedia, and more — behind a small, correct React interface instead of code you write from scratch. Every hook is SSR- and hydration-safe: nothing touches window, document, or navigator outside an effect or a guarded check, so server renders never throw and hydration never mismatches.

Every hook also ships as its own standalone, side-effect-free module — import it on its own and it tree-shakes cleanly. Public hooks never import each other; some share small internal (_-prefixed) helper modules, so pulling in useIsMobile never drags in unrelated hook code.

Installation

npm install @zap-studio/react-hooks

Quick Start

Import hooks from the top-level package, or from their own subpath if you want the narrowest possible import:

import { useIsMobile, useMediaQuery } from "@zap-studio/react-hooks";
// or, equivalently:
// import { useIsMobile } from "@zap-studio/react-hooks/sensors/use-is-mobile";
// import { useMediaQuery } from "@zap-studio/react-hooks/sensors/use-media-query";

function Nav() {
  const isMobile = useIsMobile(); // true below 768px
  const prefersDark = useMediaQuery("(prefers-color-scheme: dark)");

  return isMobile ? <MobileNav dark={prefersDark} /> : <DesktopNav dark={prefersDark} />;
}

Both hooks are SSR-safe: they return false until the client subscribes to matchMedia, so server-rendered and first-client-render output match — no hydration warnings.

Available Hooks

Every hook is also importable from its own category subpath — see Conventions.

Sensors (sensors/)

| Hook | What it does | | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | | useMediaQuery | Matches the viewport against an arbitrary CSS media query string | | useIsMobile | true below a breakpoint (768px by default) — built on useMediaQuery | | useIsClient | true only after the client has mounted — SSR hydration guard | | useIsServer | true only during server rendering — the inverse of useIsClient | | useColorScheme | "dark" or "light", from (prefers-color-scheme: dark) | | usePrefersDarkMode | Boolean shorthand for useColorScheme() === "dark" | | useOnlineStatus | Tracks navigator.onLine, updating on online/offline events | | useNetworkState | useOnlineStatus plus navigator.connection info where supported | | usePreferredLanguage | navigator.language/navigator.languages, updating on languagechange | | useOrientation | screen.orientation's angle/type, updating on orientation change | | useGeolocation | Wraps navigator.geolocation; one-shot by default, watch: true for continuous updates | | useBattery | Wraps the Battery Status API (Chromium-only); supported: false elsewhere | | useWindowSize | window.innerWidth/innerHeight, updating on resize | | useDocumentVisibility | document.visibilityState, updating on visibilitychange | | usePageLeave | Calls a handler when the pointer leaves the viewport — exit-intent UI | | useShare | Wraps the Web Share API (navigator.share), with canShare feature-detection | | usePermission | navigator.permissions.query({ name })'s state for a given permission | | useVibrate | Wraps navigator.vibrate() — mostly Android Chrome; no-op elsewhere | | useWakeLock | Screen Wake Lock; auto-released on tab hide/unmount | | useStorageEstimate | navigator.storage.estimate()'s usage/quota, one-shot on mount | | useDeviceCapabilities | navigator.hardwareConcurrency/deviceMemory (latter Chromium-only) | | useDeviceOrientation | Accelerometer/magnetometer tilt; iOS requires a requestPermission() gesture | | useDeviceMotion | Accelerometer motion; same iOS permission caveat as useDeviceOrientation | | useVisualViewport | window.visualViewport; captures on-screen-keyboard shrink innerHeight misses | | useDevicePixelRatio | window.devicePixelRatio, updating on zoom/monitor moves | | useTouchSupport | true when navigator.maxTouchPoints > 0 | | useCookieEnabled | navigator.cookieEnabled | | usePrintMode | true while printing, via the print media query | | useNotificationPermission | Notifications API permission state + a notify() trigger | | useFontsReady | true once document.fonts.ready resolves — avoid FOUC on custom web fonts | | useBrowserEngine | "blink"/"gecko"/"webkit"/"unknown", via feature detection — no navigator.userAgent parsing | | useExperimentalIdleDetector | Idle Detection API — userState/screenState (Experimental, Chromium-only) | | useExperimentalLocalFonts | Local Font Access API — window.queryLocalFonts() (Experimental, Chromium-only) | | useExperimentalNfc | Web NFC API — scan, write, and lock NDEF tags (Experimental, Chromium on Android only) | | useExperimentalAccelerometer | Generic Sensor API Accelerometer — acceleration incl. gravity (Experimental, Chromium-only) | | useExperimentalLinearAccelerationSensor | Generic Sensor API LinearAccelerationSensor — acceleration excl. gravity (Experimental, Chromium-only) | | useExperimentalGravitySensor | Generic Sensor API GravitySensor — gravity component of acceleration (Experimental, Chromium-only) | | useExperimentalGyroscope | Generic Sensor API Gyroscope — angular velocity (Experimental, Chromium-only) | | useExperimentalMagnetometer | Generic Sensor API Magnetometer — ambient magnetic field (Experimental, Chromium-only) | | useExperimentalAbsoluteOrientationSensor | Generic Sensor API AbsoluteOrientationSensor — rotation quaternion, geomagnetic north (Experimental, Chromium-only) | | useExperimentalRelativeOrientationSensor | Generic Sensor API RelativeOrientationSensor — rotation quaternion, no magnetometer (Experimental, Chromium-only) | | useExperimentalAmbientLightSensor | Generic Sensor API AmbientLightSensor — ambient light in lux (Experimental, Chromium-only) | | useExperimentalUserAgentData | User-Agent Client Hints API — navigator.userAgentData's low-entropy fields (Experimental, Chromium-only) | | useExperimentalVirtualKeyboard | VirtualKeyboard API — navigator.virtualKeyboard's bounding rect (Experimental, Chromium-only) | | useExperimentalWindowManagement | Window Management API — multi-screen details + getScreenDetails() (Experimental, Chromium-only) |

DOM / element interaction (dom/)

| Hook | What it does | | ------------------------------ | -------------------------------------------------------------------------------- | | useClickOutside | Detects a click/touch outside a ref'd element | | useHover | Boolean hover state for a ref'd element | | useMousePosition | Pointer clientX/clientY/pageX/pageY/screenX/screenY, via mousemove | | useIntersectionObserver | Element visibility in the viewport (also exported as useInView) | | useResizeObserver | Element content-box size tracking via ResizeObserver | | useEventListener | Typed, auto-cleanup addEventListener wrapper | | useLockBodyScroll | Locks body scroll (modals/drawers) | | useFavicon | Imperatively swaps the <link rel="icon"> href | | useScript | Loads an external <script src> on demand, dedupes concurrent requests | | useScrollPosition | window.scrollX/scrollY, updating on the scroll event | | useTextSelection | The current page text selection, via window.getSelection() | | useFilePicker | File System Access API (Chromium-only) | | useFileDrop | Drag-and-drop file upload state (also exported as useDropzone) | | useMutationObserver | MutationObserver on a ref'd element/subtree | | useFullscreen | Fullscreen API wrapper for a ref'd element | | usePointer | Unified mouse/touch/pen position via Pointer events | | usePopover | Native Popover API open/close state | | useViewTransition | document.startViewTransition() wrapper | | useExperimentalContactPicker | Contact Picker API — navigator.contacts.select() (Experimental, Chromium-only) | | useExperimentalEyeDropper | EyeDropper API, single-shot color picker (Experimental, Chromium-only) |

Input (input/)

| Hook | What it does | | ------------------- | ------------------------------------------------------------------------------ | | useKeyPress | true while any of the given key(s) is held down | | useHotkeys | Registers "ctrl+s"-style keyboard shortcut combos mapped to handlers | | useIdle | true after a period of no mouse/keyboard/touch/scroll activity | | useGamepad | Connected gamepads, via navigator.getGamepads() + connect/disconnect events | | useUserActivation | navigator.userActivation's isActive/hasBeenActive — gate autoplay/popups | | usePointerLock | Pointer Lock API wrapper for a ref'd element (games, drag-to-look UIs) |

Media (media/)

| Hook | What it does | | ---------------------------------- | -------------------------------------------------------------------------------- | | useUserMedia | Wraps getUserMedia() for arbitrary audio/video constraints | | useCamera | useUserMedia convenience wrapper for the common webcam case | | useScreenCapture | Wraps getDisplayMedia() — screen/window/tab sharing | | useMediaRecorder | Wraps the MediaStream Recording API around an existing stream | | useSpeechSynthesis | Wraps the Web Speech API's synthesis half — text-to-speech | | useSpeechRecognition | Wraps the Web Speech API's recognition half — voice input (Chromium/Safari only) | | usePictureInPicture | Picture-in-Picture wrapper for a ref'd <video> | | useExperimentalBarcodeDetector | Barcode Detection API — BarcodeDetector.detect() (Experimental, Chromium-only) | | useExperimentalSelectAudioOutput | MediaDevices.selectAudioOutput() device picker (Experimental, Chromium-only) |

History & Navigation (navigation/)

| Hook | What it does | | ---------------------- | ------------------------------------------------------------------------------- | | usePopState | popstate event — location.pathname/history.state on back/forward | | useNavigationType | Classifies the current entry: navigate/reload/back_forward/prerender | | useNavigation | Wraps the Navigation API's window.navigation (Chromium-only) | | useNavigationBlocker | Blocks/confirms in-app route transitions via the Navigation API (Chromium-only) |

Network (network/)

| Hook | What it does | | ----------------- | ------------------------------------------------------------------------ | | useWebSocket | WebSocket connection state + send()/close(), latest message received | | useEventSource | Server-Sent Events connection state + latest message data | | useWebTransport | WebTransport (HTTP/3) connection state + datagrams and streams |

PWA (pwa/)

| Hook | What it does | | ------------------- | ------------------------------------------------------------------------------ | | useInstallPrompt | Custom "Add to Home Screen" UI, wrapping beforeinstallprompt/appinstalled | | useServiceWorker | Service Worker registration + update-available state | | useAppBadge | Wraps the Badging API (navigator.setAppBadge/clearAppBadge) | | useStandaloneMode | Whether the app is running standalone (installed as a PWA), via display-mode |

Commerce (commerce/)

| Hook | What it does | | ------------------- | -------------------------------------------------------------- | | usePaymentRequest | Wraps the Payment Request API — shows the native payment sheet |

Lifecycle (lifecycle/)

| Hook | What it does | | --------------------------- | --------------------------------------------------------------------------- | | useMount | Runs an effect exactly once, on mount | | useUnmount | Runs a cleanup exactly once, on unmount | | useUpdateEffect | useEffect that skips the first (mount) run | | useIsomorphicLayoutEffect | useLayoutEffect on the client, useEffect on the server — no SSR warning | | useTimeout | Declarative setTimeout with cleanup | | useInterval | Declarative setInterval with cleanup | | useAsync | Wraps a promise-returning function with loading/error/data state | | useBeforeUnload | beforeunload event — "unsaved changes" navigation guard | | useAnimationFrame | Declarative requestAnimationFrame loop, called with delta time | | useIdleCallback | Wraps requestIdleCallback/cancelIdleCallback, with a Safari fallback | | useWebLock | Web Locks API — async mutual exclusion across tabs | | useWorker | Offloads work to a Worker, with a promise-based run() | | usePerformanceObserver | Wraps PerformanceObserver — long tasks, paint timing, and more |

State (state/)

| Hook | What it does | | --------------------- | -------------------------------------------------------------------------------- | | useToggle | Boolean state with a toggle() function | | useDebounce | Debounced wrapper around a callback | | useDebouncedValue | Debounces a value directly, without a separate handler | | useThrottle | Throttled wrapper around a callback (leading edge) | | useThrottledValue | Throttles a value directly (leading edge) | | useLocalStorage | State synced to localStorage, with cross-tab sync | | useSessionStorage | State synced to sessionStorage | | useIndexedDB | State synced to IndexedDB — structured values, async read/write | | usePrevious | The value from the previous render | | useCopyToClipboard | Clipboard write helper + copied state | | useCounter | Increment/decrement/reset counter state, with optional min/max | | useTheme | "light"/"dark"/"system" mode with persistence, layered on useColorScheme | | useMap | Map-backed state with set/delete/clear/get/has | | useSet | Set-backed state with add/delete/clear/has | | useQueue | FIFO queue state — enqueue/dequeue/clear, first/last accessors | | useHistoryState | State with undo/redo, over a bounded history stack | | useSearchParams | State synced to the URL query string (URLSearchParams) | | useHashState | State synced to location.hash, via the hashchange event | | useCookie | A single cookie's value, via the Cookie Store API (Chromium-only) | | useCredential | Wraps the Credential Management API (navigator.credentials) | | useBroadcastChannel | Pub/sub state shared across same-origin tabs/workers | | useWindowMessage | Cross-origin window/iframe/popup messaging (postMessage) |

Debug / observability (debug/)

| Hook | What it does | | ------------------------- | ------------------------------------------------------------------------------- | | useRenderCount | Render count for the calling component instance | | useWhyDidYouUpdate | Logs which props changed to cause the current render | | useIsFirstRender | true only on the mount render | | useUnstableRenderReason | Classifies the render cause — mount/props/state/context/parent | | useUnstableFiber | The nearest Fiber node for a ref'd DOM element, via a private react-dom pointer | | useRenderDuration | Wraps React <Profiler>'s onRender timing as a hook | | useOwnerStack | Wraps React 19's captureOwnerStack debug API |

Every hook's TSDoc includes a runnable usage example — hover it in your editor for a quick reference without leaving your code.

More hooks land incrementally.

Conventions

  • Every hook is available both from the top-level package and from its own subpath (@zap-studio/react-hooks/sensors/use-is-mobile) — same function either way.

Unstable vs. Experimental

Two different name markers warn about two different kinds of risk:

  • Unstable — the hook reads a private API with no public type, not guaranteed to stay the same shape between React versions. Only useUnstableFiber and useUnstableRenderReason carry this marker: they walk react-dom's internal Fiber tree, the same private structure React DevTools itself reads. The risk is this package breaking on a React upgrade, not the browser.
  • Experimental — the hook wraps a browser Web API that MDN itself badges "Experimental," like the Idle Detection API or Web NFC (useExperimentalIdleDetector, useExperimentalNfc, ...). The hook's own code is stable; the risk is that the browser API can change or ship in fewer browsers than you'd like. Every useExperimental* hook still fails closed — supported: false (or the equivalent) wherever the underlying API doesn't exist, instead of throwing.

A hook with neither marker is built entirely on public, standard APIs — safe to treat like any other hook in the package.