@kala-ui/react-hooks
v0.1.0-beta.2
Published
Collection of reusable React hooks for kala-ui
Maintainers
Readme
@kala-ui/react-hooks
37 reusable React hooks for building modern web applications — the foundation
layer of Kala UI (@kala-ui/react
depends on this package). SSR-safe, React 19 typed, zero dependencies.
Installation
pnpm add @kala-ui/react-hooks@beta
# or
npm install @kala-ui/react-hooks@betaRequires React 19.2+ (peer dependency).
React Native & no-DOM environments
The root entry (@kala-ui/react-hooks) is web-only. For React Native (or
any DOM-free runtime) import the portable subset instead — it is guaranteed
to contain no window/document/navigator access and no react-dom
import (enforced by a fail-closed test):
import { useUncontrolled } from "@kala-ui/react-hooks/portable";Portable hooks: useCounter, useDebounce, useDisclosure, useListState,
useMergedRef (+ mergeRefs, assignRef), useMounted, usePagination,
usePrevious, useToggle, useUncontrolled, useCallbackRef.
Hooks
State & values
useToggle— boolean state with a toggle helperuseCounter— counter with min/max/step guardsuseDisclosure— boolean state withopen/close/togglehandlersusePrevious— previous value of stateuseUncontrolled— controlled/uncontrolled duality (valueordefaultValue)useListState— array state with item-level operationsusePagination— page/total logic with navigation helpers
Debounce & timing
useDebounce— debounced callbackuseDebouncedValue— debounced value with cancel supportuseInterval— declarativesetInterval(reactiveactivestate)useTimeout— declarativesetTimeoutuseIdle— user-idle detection with a configurable timeout
Elements & DOM
useClickOutside—(handler, { events, ignore })options API, stable refuseHover— hover state via a stable callback refuseFocusTrap— focus trap for portals that mount lateuseElementSize— ResizeObserver size via a callback refuseIntersection— IntersectionObserver entry via a callback refuseMergedRef— merge multiple refs (React 19RefObject<T | null>aware)useMouse— mouse position (MousePosition)useMove— pointer move/drag with clamped 2D position (clamputil included)useViewportSize— viewport width/heightuseWindowScroll— window scroll positionuseWindowEvent— typed window event listeneruseScrollLock— reference-counted body scroll lock
Browser APIs
useClipboard— copy to clipboard with settled feedbackuseLocalStorage/useSessionStorage— state synced to storageuseMediaQuery— media query subscription (SSR-safe guards)useColorScheme—prefers-color-schemesubscriptionuseNetwork— online status and connection infouseReducedMotion—prefers-reduced-motionsubscriptionuseOs— best-effort OS detection (OStype)useDocumentTitle— set the document title, restore on unmountuseHotkeys— keyboard shortcuts (HotkeyItembindings)
SSR & utilities
useIsomorphicEffect—useLayoutEffecton the client, no-op on the serveruseMounted— mounted flaguseCallbackRef— stable ref for the latest callback
Usage examples
useDebouncedValue
import { useDebouncedValue } from "@kala-ui/react-hooks";
import { useEffect, useState } from "react";
function SearchInput() {
const [value, setValue] = useState("");
const debouncedValue = useDebouncedValue(value, 500);
useEffect(() => {
if (debouncedValue) searchApi(debouncedValue);
}, [debouncedValue]);
return <input value={value} onChange={(e) => setValue(e.target.value)} placeholder="Search…" />;
}useLocalStorage
import { useLocalStorage } from "@kala-ui/react-hooks";
function ThemeToggle() {
const [theme, setTheme] = useLocalStorage({
key: "app-theme",
defaultValue: "light",
});
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Current theme: {theme}
</button>
);
}useMediaQuery
import { useMediaQuery } from "@kala-ui/react-hooks";
function ResponsiveComponent() {
const isMobile = useMediaQuery("(max-width: 768px)");
return <div>{isMobile ? "Mobile" : "Desktop"} view</div>;
}useDisclosure
import { useDisclosure } from "@kala-ui/react-hooks";
function ModalExample() {
const [opened, { open, close }] = useDisclosure(false);
return (
<div>
<button onClick={open}>Open Modal</button>
{opened && (
<div className="modal">
<h2>Modal Content</h2>
<button onClick={close}>Close</button>
</div>
)}
</div>
);
}useClickOutside
import { useState } from "react";
import { useClickOutside } from "@kala-ui/react-hooks";
function Dropdown() {
const [opened, setOpened] = useState(false);
const ref = useClickOutside<HTMLDivElement>(() => setOpened(false));
return (
<div ref={ref}>
<button onClick={() => setOpened(!opened)}>Toggle Dropdown</button>
{opened && <div className="dropdown-menu">…</div>}
</div>
);
}useClipboard
import { useClipboard } from "@kala-ui/react-hooks";
function CopyButton({ text }: { text: string }) {
const { copy, copied } = useClipboard({ timeout: 2000 });
return <button onClick={() => copy(text)}>{copied ? "✓ Copied!" : "Copy"}</button>;
}useIntersection
import { useIntersection } from "@kala-ui/react-hooks";
function LazyImage({ src, alt }: { src: string; alt: string }) {
const { ref, entry } = useIntersection({
threshold: 0.1,
disconnectOnIntersect: true,
});
return <div ref={ref}>{entry?.isIntersecting ? <img src={src} alt={alt} /> : <div className="placeholder" />}</div>;
}useInterval
import { useInterval } from "@kala-ui/react-hooks";
function AutoRefresh() {
const interval = useInterval(() => refetch(), 5000, { autoInvoke: true });
return (
<button onClick={interval.toggle}>
{interval.active ? "Pause" : "Resume"} refresh
</button>
);
}useElementSize
import { useElementSize } from "@kala-ui/react-hooks";
function ResponsiveComponent() {
const [ref, { width, height }] = useElementSize<HTMLDivElement>();
return (
<div ref={ref} style={{ resize: "both", overflow: "auto", border: "1px solid" }}>
<p>
{Math.round(width)}×{Math.round(height)}px — resize me!
</p>
</div>
);
}usePrevious
import { useState } from "react";
import { usePrevious } from "@kala-ui/react-hooks";
function Counter() {
const [count, setCount] = useState(0);
const previousCount = usePrevious(count);
return (
<div>
<p>Current: {count}</p>
<p>Previous: {previousCount}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}License
MIT
