Skip to main content

Command Palette

Search for a command to run...

Custom React hooks exported from @ngrok/mantle/hooks.

Overview

HookDescription
useBreakpointReturns the current responsive breakpoint based on the viewport width.
useIsBelowBreakpointReturns true if the viewport width is below a given breakpoint.
useCallbackRefReturns a memoized callback that always refers to the latest function passed to it.
useCopyToClipboardCopies a string to the clipboard.
useDebouncedCallbackCreates a debounced version of a callback function.
useIsHydratedReturns whether the component tree has been hydrated on the client.
useIsomorphicLayoutEffectUses useLayoutEffect on the client and useEffect on the server.
useMatchesMediaQuerySubscribes to and returns the result of a CSS media query.
usePrefersReducedMotionReturns true when the user prefers reduced motion.
useRandomStableIdGenerates a random, stable ID safe for CSS selectors and element IDs.
useScrollBehaviorReturns a ScrollBehavior that respects the user's reduced motion preference.

useBreakpoint

Returns the current responsive breakpoint based on the viewport width. Uses a singleton subscription to a set of min-width media queries and returns the largest matching breakpoint.

import { useBreakpoint } from "@ngrok/mantle/hooks";

function ResponsiveComponent() {
	const breakpoint = useBreakpoint();
	return <p>Current breakpoint: {breakpoint}</p>;
}

useIsBelowBreakpoint

Returns true if the current viewport width is below the specified breakpoint. Accepts a TailwindBreakpoint ("2xs", "xs", "sm", "md", "lg", "xl", "2xl").

import { useIsBelowBreakpoint } from "@ngrok/mantle/hooks";

function ResponsiveSidebar() {
	const isMobile = useIsBelowBreakpoint("md");
	return isMobile ? <MobileNav /> : <DesktopNav />;
}

useCallbackRef

Returns a memoized callback that always refers to the latest callback passed to the hook. Useful when passing a callback that may or may not be memoized to a child component without causing re-renders.

import { useCallbackRef } from "@ngrok/mantle/hooks";

function Example({ onChange }: { onChange?: (value: string) => void }) {
	const stableOnChange = useCallbackRef(onChange);
	// stableOnChange always calls the latest onChange
}

useCopyToClipboard

Copies a string to the clipboard. Returns a tuple of the last copied value and a copy function. Includes a fallback for older browsers.

import { useCopyToClipboard } from "@ngrok/mantle/hooks";

function CopyButton({ text }: { text: string }) {
	const [copiedValue, copy] = useCopyToClipboard();
	return (
		<button onClick={() => copy(text)}>
			{copiedValue === text ? "Copied!" : "Copy"}
		</button>
	);
}

useDebouncedCallback

Creates a debounced version of a callback function. Delays execution until a period of inactivity has passed (options.waitMs). The debounced callback is stable and safe to use in dependency arrays.

import { useDebouncedCallback } from "@ngrok/mantle/hooks";

function SearchInput() {
	const debouncedSearch = useDebouncedCallback(
		(query: string) => fetchResults(query),
		{ waitMs: 300 },
	);
	return <input onChange={(event) => debouncedSearch(event.target.value)} />;
}

useIsHydrated

Returns whether the component tree has been hydrated on the client. Returns false on the server and true after hydration on the client. Uses useSyncExternalStore to prevent hydration mismatches.

import { useIsHydrated } from "@ngrok/mantle/hooks";

function ClientOnly({ children }: { children: React.ReactNode }) {
	const isHydrated = useIsHydrated();
	if (!isHydrated) {
		return <span style={{ visibility: "hidden" }}>Loading…</span>;
	}
	return <>{children}</>;
}

useIsomorphicLayoutEffect

Uses useLayoutEffect on the client and useEffect on the server. Avoids SSR warnings about useLayoutEffect doing nothing on the server.

import { useIsomorphicLayoutEffect } from "@ngrok/mantle/hooks";

function MeasureElement() {
	useIsomorphicLayoutEffect(() => {
		// safely measure DOM on client, no-op on server
	}, []);
}

useMatchesMediaQuery

Subscribes to and returns the result of a CSS media query string. Uses window.matchMedia and useSyncExternalStore for concurrent rendering compatibility.

import { useMatchesMediaQuery } from "@ngrok/mantle/hooks";

function DarkModeDetector() {
	const isDark = useMatchesMediaQuery("(prefers-color-scheme: dark)");
	return <p>Dark mode: {isDark ? "Yes" : "No"}</p>;
}

usePrefersReducedMotion

Returns true when the user has opted out of animations (i.e., prefers reduced motion). Defaults to true on the server to avoid animating before hydration.

import { usePrefersReducedMotion } from "@ngrok/mantle/hooks";

function AnimatedComponent() {
	const reduce = usePrefersReducedMotion();
	const duration = reduce ? 0 : 200;
	return <div style={{ transitionDuration: duration + "ms" }} />;
}

useRandomStableId

Generates a random, stable ID. Similar to useId, but produces an ID that is safe for use in CSS selectors and as element IDs. Accepts an optional prefix (defaults to "mantle").

import { useRandomStableId } from "@ngrok/mantle/hooks";

function Tooltip({ children }: { children: React.ReactNode }) {
	const id = useRandomStableId("tooltip");
	return <div id={id}>{children}</div>;
}

useScrollBehavior

Returns a ScrollBehavior ("auto" or "smooth") that respects the user's reduced motion preference. Returns "auto" when the user prefers reduced motion, otherwise "smooth".

import { useScrollBehavior } from "@ngrok/mantle/hooks";

function ScrollToTop() {
	const behavior = useScrollBehavior();
	return (
		<button onClick={() => window.scrollTo({ top: 0, behavior })}>
			Back to top
		</button>
	);
}