Introducing Flights: agent-native data pipelines in MotherDuckJoin the livestream

Skip to main content

useDiveState hook

The useDiveState hook is a React hook for storing interactive state in a Dive. 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

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

Syntax

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:

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

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

Parameters

key

TypeRequired
stringYes

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

TypeRequired
JSON-serializable valueYes

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

PropertyTypeDescription
valuesame type as initialValueThe current value for key, read from the URL state when present.
setValuefunctionUpdates 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.

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

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

Examples

Share a filtered view

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

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

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.