---
sidebar_position: 1.1
title: useDiveState hook
description: React hook for storing shareable UI state in MotherDuck Dives.
feature_stage: preview
---

The `useDiveState` hook is a React hook for storing interactive state in a [Dive](/key-tasks/ai-and-motherduck/dives). It works like React's `useState`, but it also syncs the value to the Dive URL so viewers can share the current filters, sorting, selected view, and drill-down state.

Use `useDiveState` for state that should survive a page refresh or travel with a shared URL. Use React's `useState` for temporary UI state such as input drafts, open dialogs, or dismissed banners.

## Import

```jsx
import { useDiveState } from "@motherduck/react-sql-query";
```

## Syntax

```jsx
const [value, setValue] = useDiveState(key, initialValue);
```

In TypeScript, pass the value type explicitly when you want a narrowed literal union or when the initial value is an empty array:

```tsx
const [view, setView] = useDiveState<"cards" | "list" | "compact">(
  "view",
  "cards"
);

const [selectedRegions, setSelectedRegions] = useDiveState<string[]>(
  "selectedRegions",
  []
);
```

## Parameters

### `key`

| Type | Required |
|------|----------|
| `string` | Yes |

A stable key for the state value. If two components in the same Dive call `useDiveState` with the same key, they read and write the same value.

Use short, descriptive keys such as `region`, `sortBy`, `selectedCustomer`, or `chartView`.

### `initialValue`

| Type | Required |
|------|----------|
| JSON-serializable value | Yes |

The value to use when the URL does not contain a value for `key`.

Values must be JSON-serializable: strings, numbers, booleans, `null`, arrays, and plain objects made from those values. Do not store `Date`, `Map`, `Set`, functions, class instances, credentials, or large row payloads in Dive state.

If TypeScript reports `Expected 3 arguments, but got 2` with a label about `useDiveState defaultValue must be JSON-serializable`, the `initialValue` is not a supported JSON value. There is no third argument to pass; change the value shape instead.

## Return value

| Property | Type | Description |
|----------|------|-------------|
| `value` | same type as `initialValue` | The current value for `key`, read from the URL state when present. |
| `setValue` | function | Updates the value and syncs it to the URL state. |

`setValue` accepts either a new value or an updater function, matching React's `useState` setter pattern.

## Reset state

Pass `undefined` to the setter to remove the key from the URL state. After reset, the hook reads from `initialValue` again.

```jsx
const [view, setView] = useDiveState("view", "summary");

return (
  <button type="button" onClick={() => setView(undefined)}>
    Reset view
  </button>
);
```

## Examples

### Share a filtered view

```jsx
import { useDiveState, useSQLQuery } from "@motherduck/react-sql-query";

export default function Dive() {
  const [region, setRegion] = useDiveState("region", "all");

  const regionFilter = {
    all: "",
    amer: "WHERE region = 'Americas'",
    emea: "WHERE region = 'EMEA'",
    apac: "WHERE region = 'APAC'",
  }[region] ?? "";

  const { data = [], isLoading } = useSQLQuery(`
    SELECT region, SUM(revenue) AS revenue
    FROM "analytics"."main"."orders"
    ${regionFilter}
    GROUP BY ALL
    ORDER BY revenue DESC
  `);

  if (isLoading) return <div>Loading...</div>;

  return (
    <div>
      <select value={region} onChange={(event) => setRegion(event.target.value)}>
        <option value="all">All regions</option>
        <option value="amer">Americas</option>
        <option value="emea">EMEA</option>
        <option value="apac">APAC</option>
      </select>

      <ul>
        {data.map((row) => (
          <li key={row.region}>
            {row.region}: {Number(row.revenue).toLocaleString()}
          </li>
        ))}
      </ul>
    </div>
  );
}
```

When a viewer changes the selected region, the Dive URL updates. Copying that URL shares the same selected region with another viewer.

### Share sort order

```jsx
const [sortBy, setSortBy] = useDiveState("sortBy", "revenue_desc");

const orderBy = {
  revenue_desc: "revenue DESC",
  revenue_asc: "revenue ASC",
  customer: "customer_name ASC",
}[sortBy] ?? "revenue DESC";
```

Use a controlled set of sort values and map them to SQL fragments. Do not concatenate unrestricted user input into SQL.

### Share state between components

```jsx
function RegionPicker() {
  const [region, setRegion] = useDiveState("region", "all");
  return <select value={region} onChange={(event) => setRegion(event.target.value)} />;
}

function RevenueChart() {
  const [region] = useDiveState("region", "all");
  return <Chart title={`Revenue for ${region}`} />;
}
```

Both components use the same `region` key, so changing the picker updates the chart without a separate context provider.

## Limitations

- State is visible to anyone who has the URL. Do not store secrets, tokens, credentials, or sensitive row-level data.
- Keep state small. Large encoded state creates long URLs that may be truncated by chat apps, browsers, or URL shorteners.
- Store selections and identifiers, not full query results. If the value can be reconstructed from SQL, store the small key and query for the data again.
- URL state is per Dive view. It does not change the saved Dive source or its default `initialValue`.

## Related

- [`useSQLQuery` hook](/sql-reference/motherduck-sql-reference/ai-functions/dives/use-sql-query) — Query data from within Dives
- [Creating visualizations with Dives](/key-tasks/ai-and-motherduck/dives) — How-to guide
- [Embedding Dives in your web application](/key-tasks/ai-and-motherduck/dives/embedding-dives) — Handle embedded Dive sessions and exports
- [MCP Server](/sql-reference/mcp/) — Create Dives through AI assistants


---

## Docs feedback

MotherDuck accepts optional user-submitted feedback about this page at `POST https://motherduck.com/docs/api/feedback/agent`.
For agents and automated tools, feedback submission should be user-confirmed before sending.

Payload:

```json
{
  "page_path": "/sql-reference/motherduck-sql-reference/ai-functions/dives/use-dive-state/",
  "page_title": "useDiveState hook",
  "text": "<the user's feedback, max 2000 characters>",
  "source": "<optional identifier for your interface, for example 'claude.ai' or 'chatgpt'>"
}
```

`page_path` and `text` are required; `page_title` and `source` are optional. Responses: `200 {"feedback_id": "<uuid>"}`, `400` for malformed payloads, and `429` when rate-limited.
