---
sidebar_position: 1
title: useSQLQuery hook
description: React hook for querying MotherDuck data from within Dives.
feature_stage: preview
---

The `useSQLQuery` hook is a React hook that runs SQL queries against MotherDuck from within a [Dive](/key-tasks/ai-and-motherduck/dives). It handles loading states and error reporting so your component can focus on rendering data.

## Import

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

## Syntax

```jsx
const { data, isLoading, isError, error, exportAs } = useSQLQuery(sql, options);
```

## Parameters

### `sql`

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

A SQL query string to run against MotherDuck. The Dive runtime returns rows as objects.

Use fully qualified, double-quoted table names in your queries (`"database"."schema"."table"`) to avoid issues when the Dive runs outside your current database context.

### `options`

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `enabled` | `boolean` | `true` | Set to `false` to skip query execution. Useful when a query depends on user input that isn't available yet. |

## Return value

| Property | Type | Description |
|----------|------|-------------|
| `data` | array or `undefined` | Query result as an array of row objects. `undefined` while loading. |
| `isLoading` | `boolean` | `true` while the query is running |
| `isError` | `boolean` | `true` if the query failed |
| `error` | `Error` or `null` | Error object if the query failed |
| `exportAs` | `(options) => Promise<void>` | Exports this query as a file |

:::warning
`data` is the row array directly — there is no `data.rows` wrapper. Always guard against `undefined`:

```jsx
const rows = Array.isArray(data) ? data : [];
```

:::

## Numeric values

DuckDB returns `BIGINT`, `HUGEINT`, and `DECIMAL` as JavaScript `BigInt` or special objects, not `number`. These crash if rendered in JSX or used with `.toFixed()`. Define this helper at the top of every Dive and wrap all numeric values:

```jsx
const N = (v) => (v != null ? Number(v) : 0);
```

## Examples

### Basic query

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

const N = (v) => (v != null ? Number(v) : 0);

export default function Dive() {
  const { data, isLoading } = useSQLQuery(`
    SELECT category, SUM(amount) AS total
    FROM "my_db"."main"."sales"
    GROUP BY ALL
  `);

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

  const rows = Array.isArray(data) ? data : [];

  return (
    <ul>
      {rows.map((row) => (
        <li key={row.category}>
          {row.category}: ${N(row.total).toLocaleString()}
        </li>
      ))}
    </ul>
  );
}
```

### Conditional queries with `enabled`

Skip a query until a user selection is available:

```jsx
const [selected, setSelected] = useState(null);

const { data: details } = useSQLQuery(`
  SELECT * FROM "my_db"."main"."products"
  WHERE category = '${selected}'
`, { enabled: !!selected });
```

### Multiple independent queries

Each `useSQLQuery` call loads independently. Render the page layout immediately and show inline placeholders per section instead of a single loading spinner:

```jsx
const summary = useSQLQuery(`
  SELECT COUNT(*) AS total, SUM(revenue) AS revenue
  FROM "my_db"."main"."orders"
`);

const monthly = useSQLQuery(`
  SELECT strftime(date_trunc('month', order_date), '%Y-%m') AS month,
         SUM(revenue) AS revenue
  FROM "my_db"."main"."orders"
  GROUP BY 1 ORDER BY 1
`);

return (
  <div>
    {summary.isLoading
      ? <div className="h-12 w-24 bg-gray-200 animate-pulse rounded" />
      : <p>{N(summary.data?.[0]?.revenue)}</p>
    }
    {monthly.isLoading
      ? <div className="bg-gray-100 animate-pulse rounded" style={{height: 250}} />
      : <MyChart data={monthly.data} />
    }
  </div>
);
```

### Export query results

Use `exportAs()` when you want to export the same SQL query used by a `useSQLQuery()` hook. Exports must start from a user action, such as a button click.

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

export default function Dive() {
  const orders = useSQLQuery(`
    SELECT *
    FROM "my_db"."main"."orders"
    ORDER BY order_date DESC
  `);

  return (
    <button
      type="button"
      disabled={orders.isLoading}
      onClick={() =>
        orders.exportAs({
          format: "csv",
          title: "Orders",
          filename: "orders",
        })
      }
    >
      Export orders
    </button>
  );
}
```

Use `useExport()` when the export should run a different SQL query than the rows rendered on the page:

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

export default function DiveExportButton() {
  const { exportQuery } = useExport();

  return (
    <button
      type="button"
      onClick={() =>
        exportQuery({
          sql: `
            SELECT customer_id, SUM(amount) AS total_amount
            FROM "my_db"."main"."orders"
            GROUP BY ALL
            ORDER BY total_amount DESC
          `,
          format: "parquet",
          title: "Customer totals",
          filename: "customer-totals",
        })
      }
    >
      Export customer totals
    </button>
  );
}
```

Supported formats are `csv`, `json`, `parquet`, and `xlsx`. The `filename` value is a base name; MotherDuck adds the file extension. You can pass DuckDB `COPY` writer options under `csv`, `json`, `parquet`, or `xlsx`:

```jsx
orders.exportAs({
  format: "xlsx",
  filename: "orders",
  xlsx: {
    sheet: "Orders",
    header: true,
  },
});
```

Exports run with DuckDB `COPY TO`, not from the rows already materialized in React. This means an export can contain more rows than the Dive renders. Export SQL must be read-only.

## Tips

- **Format dates in SQL, not JavaScript.** Use DuckDB's `strftime()` to format dates and timestamps as strings. DuckDB date types are returned as special objects that don't render in JSX.
- **Fill time series gaps in SQL.** Recharts does not interpolate missing time periods. Use `generate_series` with a `LEFT JOIN` to produce a continuous date spine.
- **Dollar-quoted strings.** When passing Dive content through SQL (for example, with [`MD_CREATE_DIVE`](../md-create-dive)), use [dollar-quoted string literals](https://duckdb.org/docs/stable/sql/data_types/literal_types#dollar-quoted-string-literals) to avoid escaping issues with nested quotes.

## Related

- [Dives SQL functions](/sql-reference/motherduck-sql-reference/ai-functions/dives/) — Manage Dives with SQL
- [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-sql-query/",
  "page_title": "useSQLQuery 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.
