@ox0/hooks
v0.2.1
Published
Shared React hooks for ox0 applications. Every hook is SSR-safe unless noted.
Downloads
332
Readme
@ox0/hooks
Shared React hooks for ox0 applications. Every hook is SSR-safe unless noted.
Installation
pnpm add @ox0/hooksHooks
State and control
useControllableState<T>(options)
Controlled/uncontrolled state bridge. Pass value + onChange for controlled mode; omit value
for uncontrolled. When uncontrolled, internal state resets to defaultValue whenever defaultValue
changes — pass stable primitives to avoid surprise resets.
const [val, setVal] = useControllableState({ defaultValue: "initial", value, onChange })useDisclosure(initialOpen?)
Boolean open/close state. Returns { isOpen, onOpen, onClose, onToggle } with stable callbacks.
const { isOpen, onOpen, onClose } = useDisclosure()Persistence
All storage hooks require a <StorageProvider scope={userId ?? null}> ancestor. The scope string
prefixes every key so different users sharing a browser do not mix data. Pass scope={null} to
opt out of scoping.
import { StorageProvider } from "@ox0/hooks"
function App() {
return <StorageProvider scope={currentUserId}>{children}</StorageProvider>
}usePersistentState(key, parser, options)
localStorage, tab-private. Changes are not visible in other open tabs.
usePersistentSharedState(key, parser, options)
localStorage, shared across tabs. Changes broadcast via BroadcastChannel and the storage
event. All tabs with the same key stay in sync.
useSessionState(key, parser, options)
sessionStorage, tab-private.
useSessionSharedState(key, parser, options)
sessionStorage, shared within the tab (multiple hook instances on the same key sync via
in-tab pub/sub).
Shared options:
type StoredStateOptions<T> = {
defaultValue: T
version?: number // @default 1
migrate?: (value: unknown, fromVersion: number) => T
}Pass any object with a .parse(unknown): T method as the parser (Zod schemas work directly).
Use version + migrate to transform stored data when the shape changes:
const [theme, setTheme] = usePersistentSharedState("ui:theme", ThemeSchema, {
defaultValue: "system",
version: 2,
migrate: (old, from) => (from === 1 ? "system" : "system"),
})Interaction
usePress(props) — re-export from React Aria
Cross-device press normalization for mouse, touch, keyboard (Space/Enter), and virtual pointer
(screen reader). Handles iOS text-selection conflicts and scroll-gesture cancellation. Prefer this
over raw onClick for custom interactive elements.
useLongPress(options)
Returns pointer event handlers that fire onLongPress after the pointer has been held for
delayMs (default 500ms) without moving beyond moveThresholdPx (default 8px). Cancels on
pointer up or excessive movement; suppresses the subsequent click.
useKeyDown(key, callback, options?)
Attaches a keydown listener to window. Modifier options: ctrl, meta, shift, alt.
Pass enabled: false to disable without removing the hook call.
DOM and browser
useContainerSize<T extends HTMLElement>()
Measures element content size via ResizeObserver. Returns [ref, { width, height }]. Attach
the ref to the element to observe.
const [ref, { width }] = useContainerSize<HTMLDivElement>()useEventListener(type, listener, target?)
Attaches a typed event listener to window (default), a Document, HTMLElement, or a ref.
The listener ref is always up to date — no stale-closure issues.
useHover<T extends HTMLElement>()
Returns [ref, isHovered]. Uses pointerenter/pointerleave events.
Viewport and layout
useBreakpoint()
Tracks the current responsive breakpoint. Returns
{ breakpoint, isMobile, isTablet, isDesktop, isMobileOrTablet, isTabletOrDesktop }.
Breakpoints: mobile ≤ 767px, tablet ≤ 1023px, desktop > 1023px.
useWindowSize()
Tracks { width, height } from window.innerWidth/innerHeight.
useViewport()
Tracks { visualViewportHeight, keyboardOpen }. keyboardOpen is true when the on-screen
keyboard reduces the visual viewport by more than 150px — useful for mobile layouts.
Media queries and preferences
useMediaQuery(query)
Returns true when the CSS media query matches. Updates on change.
usePointerCoarse()
Returns true when the primary pointer is coarse (touch screen). Shorthand for
useMediaQuery("(pointer: coarse)").
usePrefersReducedMotion()
Returns true when the user has requested reduced motion.
useNavigatorOnline()
Returns true when the browser reports a network connection. Updates on online/offline events.
OS and environment
useOS(override?)
Detects the runtime OS: "mac" | "windows" | "linux" | "mobile". Useful for keyboard shortcut
labels. Pass override to force a value in tests or demos.
Timing
useDebouncedValue<T>(value, delay?)
Returns a debounced copy of value that only updates after delay ms (default 200ms) of no
changes.
useInterval(callback, delay)
Calls callback every delay ms. Pass delay = null to pause. Callback ref is always current.
useTimeout(callback, delay)
Calls callback once after delay ms. Pass delay = null to disable. Callback ref is always
current.
useNow(options?)
Returns the current Date.now() value, updating every update ms (default 1000ms). Pass
update: false to get a stable snapshot.
Utilities
usePrevious<T>(value)
Returns the value from the previous render. Returns undefined on the first render.
useCopyToClipboard(resetMs?)
Returns { copy: (text: string) => Promise<void>, copied: boolean }. copied resets to false
after resetMs ms (default 2000ms).
useBeforeUnloadWarning(enabled)
Shows the browser's native "leave page?" dialog when enabled is true. Use for unsaved-changes
guards.
useDeferredPresence(options)
Manages show/hide lifecycle with entry delay, minimum visible time, and exit duration. Returns
"hidden" | "visible" | "exiting". Useful for tooltips, toasts, and animated presence.
