Packages
@thexjs/hooks
SSR-safe React hooks for X apps. Every hook is safe inside renderToString / renderToReadableStream; none of them touch window, document, localStorage, or navigator during the server render pass. DOM access only happens in effects, after hydration.
terminal
bun add @thexjs/hooks
Requires React 18 or 19. For the dev UX in a new app, choose the Hooks feature from create-thexjs-app to get it pre-installed.
Hooks
| Hook | Purpose |
|---|---|
| useDebounce(value, delayMs) | Debounced value for search inputs, resize logic, etc. |
| useLocalStorage(key, initial) | localStorage-backed state, syncs across tabs via the storage event |
| useMediaQuery(query) | Boolean from matchMedia; false on the server, hydrates on mount |
| useIntersectionObserver(ref, options) | Intersection entry for lazy-loading, infinite scroll, on-view analytics |
| useEventListener(event, handler, target?) | Typed addEventListener with automatic cleanup |
| useClickOutside(ref, handler) | Pointer-down outside a ref (dropdowns, modals) |
| usePrevious(value) | Value from the previous render |
| useCopyToClipboard() | [copiedText, copy]; async Clipboard API + execCommand fallback |
| useOnlineStatus() | navigator.onLine + online/offline listeners |
| useServerAction(fn) | { data, error, isPending } + run over a server-function client |
| useForm(initialValues, validate) | Lightweight form state + validation (zod, valibot, or hand-written) |
Example
search.tsx
import { useDebounce, useServerAction } from "@thexjs/hooks";function Search() { const [query, setQuery] = useState(""); const q = useDebounce(query, 300); return <input value={query} onChange={(e) => setQuery(e.target.value)} /> >;}function LikeButton() { // `like` is a server function from the generated client const [{ isPending, error }, run] = useServerAction(like); return <button onClick={() => run()}>{isPending ? "..." : "Like"}</button>;}Server actions
useServerAction expects an async function that talks to your server function endpoint, usually one from the client generated by @thexjs/core's build step (generateServerFunctionClient output). It adds the mutation-hook ergonomics: { data, error, isPending } plus a run(...args) trigger.