Snippets
Short, focused code snippets for React, TypeScript, and Next.js patterns I keep coming back to.
Implementation
import { useEffect, type RefObject } from "react";
export function useOnClickOutside<T extends HTMLElement>(
ref: RefObject<T | null>,
handler: (event: MouseEvent | TouchEvent) => void,
) {
useEffect(() => {
const listener = (event: MouseEvent | TouchEvent) => {
const el = ref.current;
if (!el || el.contains(event.target as Node)) return;
handler(event);
};
document.addEventListener("mousedown", listener);
document.addEventListener("touchstart", listener);
return () => {
document.removeEventListener("mousedown", listener);
document.removeEventListener("touchstart", listener);
};
}, [ref, handler]);
}Example
function UserMenu() {
const [open, setOpen] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
useOnClickOutside(menuRef, () => setOpen(false));
return (
<div ref={menuRef} className="relative">
<button type="button" onClick={() => setOpen((v) => !v)} aria-expanded={open}>
Account
</button>
{open && (
<ul role="menu" className="absolute right-0 mt-2 rounded-lg border shadow-lg">
<li role="menuitem">
<a href="/settings">Settings</a>
</li>
</ul>
)}
</div>
);
}hookseventsui
Implementation
import { useState, useEffect } from "react";
export function useMediaQuery(query: string, defaultValue = false): boolean {
const [matches, setMatches] = useState(() => {
if (typeof window === "undefined") return defaultValue;
return window.matchMedia(query).matches;
});
useEffect(() => {
const media = window.matchMedia(query);
const handler = (e: MediaQueryListEvent) => setMatches(e.matches);
setMatches(media.matches);
media.addEventListener("change", handler);
return () => media.removeEventListener("change", handler);
}, [query]);
return matches;
}Example
const isDesktop = useMediaQuery("(min-width: 1024px)");
const prefersReducedMotion = useMediaQuery(
"(prefers-reduced-motion: reduce)",
false,
);
// Skip heavy animation when user prefers reduced motion
const shouldAnimate = !prefersReducedMotion && isDesktop;hooksresponsivelayout
Implementation
import { createContext, useContext, type Context } from "react";
export function createRequiredContext<T>(displayName: string) {
const Context = createContext<T | null>(null);
Context.displayName = displayName;
function useRequiredContext() {
const value = useContext(Context);
if (value === null) {
throw new Error(`${displayName} must be used within its Provider`);
}
return value;
}
return [Context, useRequiredContext] as const;
}Example
import { useState } from "react";
type TabsContextValue = {
value: string;
setValue: (next: string) => void;
};
const [TabsContext, useTabsContext] =
createRequiredContext<TabsContextValue>("TabsContext");
function Tabs({ children }: { children: React.ReactNode }) {
const [value, setValue] = useState("home");
return (
<TabsContext.Provider value={{ value, setValue }}>
{children}
</TabsContext.Provider>
);
}
function TabsTrigger({ id, children }: { id: string; children: React.ReactNode }) {
const { value, setValue } = useTabsContext();
return (
<button type="button" aria-selected={value === id} onClick={() => setValue(id)}>
{children}
</button>
);
}hookscontextcompound-components
Implementation
import { useRef, useCallback, useLayoutEffect } from "react";
export function useStableCallback<T extends (...args: never[]) => unknown>(
callback: T,
): T {
const callbackRef = useRef(callback);
useLayoutEffect(() => {
callbackRef.current = callback;
});
return useCallback(
((...args) => callbackRef.current(...args)) as T,
[],
);
}Example
// Accordion-style toggle: empty deps, always reads latest props
const onValueChangeRef = useRef(onValueChange);
onValueChangeRef.current = onValueChange;
const toggle = useCallback((id: string) => {
onValueChangeRef.current?.(id);
}, []);
// Or with useStableCallback:
const toggle = useStableCallback((id: string) => {
onValueChange?.(id);
});hooksperformancerefs
Implementation
import { useState, useEffect } from "react";
export function useDebounce<T>(value: T, delay = 300): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}Example
const [query, setQuery] = useState("");
const debouncedQuery = useDebounce(query, 300);
useEffect(() => {
if (!debouncedQuery) return;
fetchResults(debouncedQuery);
}, [debouncedQuery]);
// input: <input value={query} onChange={(e) => setQuery(e.target.value)} />hooksperformanceinputs