---
id: "packages/active-ui/hooks"
title: "Hooks"
description: "The React hooks of active-ui - a debounced value, the client and mobile checks, code highlighting, and state in the address."
url: "/packages/active-ui/hooks"
locale: "en"
revision: "b291ac85209fb1dd"
---

# Hooks

active-ui exports a few hooks for common interface work. Import each from its own entry point,
such as `@workspace/active-ui/hooks/use-debounce`; that is the canonical form. The package root,
`@workspace/active-ui`, also re-exports all but `useSearchParamState`; there is no entry point
that gathers only the hooks.

**Example: A debounced search and the mobile width**

Client: Runs entirely in your browser. Source: `examples/ui/hooks.tsx`.

```tsx
// apps/docs/examples/ui/hooks.tsx
'use client';

import {useState} from 'react';

import {
  Field,
  FieldDescription,
  FieldLabel,
} from '@workspace/active-ui/components/primitives/field';
import {Input} from '@workspace/active-ui/components/primitives/input';
import {useDebounce} from '@workspace/active-ui/hooks/use-debounce';
import {useIsMobile} from '@workspace/active-ui/hooks/use-mobile';

/**
 * `useDebounce` follows the field 500 ms after you stop typing, so a search
 * would run once per pause. `useIsMobile` follows the window width: make the
 * window narrower than 768 px to see it change.
 */
export default function HooksExample() {
  const [query, setQuery] = useState('');
  const debounced = useDebounce(query);
  const isMobile = useIsMobile();
  return (
    <div className="grid w-full max-w-xs gap-6">
      <Field>
        <FieldLabel htmlFor="hooks-search">Search</FieldLabel>
        <Input
          id="hooks-search"
          value={query}
          onChange={(event) => setQuery(event.target.value)}
        />
        <FieldDescription aria-live="polite">
          Searching for: <code>{debounced || "''"}</code>
        </FieldDescription>
      </Field>
      <p className="text-sm">
        <code>useIsMobile()</code>: <strong>{String(isMobile)}</strong>
      </p>
    </div>
  );
}
```

## The hooks

- `useDebounce(value, delay?)`: the value, `delay` milliseconds (default 500) after it last
  changed. For a search on a pause in typing. [AsyncCombobox](/packages/active-ui/async-combobox)
  debounces itself; do not stack both.
- `useIsClient()`: `false` on the server and during hydration, `true` after it, without a
  hydration mismatch. [ClientOnly](/packages/active-ui/client-only) is built on it.
- `useIsMobile()`: whether the window is narrower than 768 px, following resizes; `false` on the
  server and in the first render. The [Sidebar](/packages/active-ui/sidebar) uses it.
- `useHighlightedCode(value, language)`: the code as one node per line for
  [CodeBlock](/packages/active-ui/code-block), plain until the highlighter has loaded, then
  highlighted for light and dark. See [Utilities](/packages/active-ui/utilities) for the languages.
- `useSearchParamState(name, defaultValue)`: one search parameter of the address as state, with the
  Next.js App Router; setting the default value removes the parameter. Imported from its own entry
  point only, because it needs Next.js. [SearchParamTabs](/packages/active-ui/responsive-tabs) is
  built on it.

## API

**useDebounce**: `<T>(value: T, delay?: number) => T`

The value, `delay` milliseconds (default 500) after it last changed: for a
search that should run when typing pauses, not on every key. Each change
restarts the wait.

**useIsClient**: `() => boolean`

React 19 hook to detect if code is running on the client.

Uses useSyncExternalStore for proper hydration handling - returns false
during SSR and initial hydration, true after hydration completes.

This is the recommended React 19 pattern for client detection,
replacing the older useEffect + useState approach.

**useIsMobile**: `() => boolean`

Whether the window is narrower than 768 px, following resizes. `false` on
the server and in the first render, so markup that depends on it hydrates
without a mismatch and then updates.

**useHighlightedCode**: `(value: string, language: string) => ReactNode[]`

Highlights code in the browser, line by line, for `CodeBlockLine`.

Returns one node per line: plain text until the highlighter and the
language have loaded, then highlighted spans with a light and a dark
colour. A language that is not listed in `lib/highlighter` stays plain.
While the code grows (a stream), only the new lines are highlighted.

**useSearchParamState**: `(paramName: string, defaultValue: string) => [string, (newState: string) => void]`

A hook that syncs a component's state with a URL search parameter.
It provides a `useState`-like interface for managing a single query parameter.
