Hooks

useDataExport

Project an in-memory array of rows through a column definition and trigger a browser download as CSV, JSON, Markdown, or XLSX.

Installation

pnpm dlx @docyrus/cli add @docyrus/hooks-use-data-export
Required Packages(1 package)
pnpm add xlsx

xlsx is loaded with a dynamic import('xlsx') and is only fetched when format === 'xlsx'. CSV, JSON, and Markdown exports work without it.

Overview

useDataExport is a generic, framework-agnostic export hook for any in-memory array. You describe how each column should be projected, and the hook handles serialization, escaping, blob construction, and the browser download.

  • Four formats — csv, json, markdown, and xlsx.
  • Column projection — pick fields, rename headers, derive values via accessor, format cells via formatter. Order is preserved in the output.
  • Safe by default — CSV cells are RFC 4180 escaped, Markdown cells escape |, \, and newlines, and Date values are written as ISO strings.
  • Excel-friendly — CSV output is prefixed with a UTF-8 BOM so Excel auto-detects encoding (toggle with csvBom).
  • SSR-safe — exportData() is a no-op on the server (typeof window === 'undefined') and returns null instead of throwing.
  • Lazy xlsx — the xlsx package is only loaded when the user actually picks the XLSX format.

Usage

Basic example

'use client';

import { Button } from '@docyrus/ui/primitives/ui/button';
import { useDataExport } from '@docyrus/ui/library/hooks/use-data-export';

type Contact = {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
};

const rows: Array<Contact> = [
  { id: '1', name: 'Ada Lovelace', email: 'ada@example.com', createdAt: new Date() }
];

export function ExportContactsButton() {
  const { exportData, isExporting } = useDataExport<Contact>({
    fileName: 'contacts',
    columns: [
      { id: 'id', header: 'ID' },
      { id: 'name', header: 'Full Name' },
      { id: 'email', header: 'Email' },
      { id: 'createdAt', header: 'Created' }
    ]
  });

  return (
    <Button
      disabled={isExporting}
      onClick={() => exportData(rows, 'csv')}>
      Export CSV
    </Button>
  );
}

Custom accessor + formatter

const { exportData } = useDataExport<Contact>({
  fileName: 'contacts',
  columns: [
    {
      id: 'fullName',
      header: 'Full Name',
      accessor: (row) => `${row.firstName} ${row.lastName}`
    },
    {
      id: 'amount',
      header: 'Amount',
      accessor: (row) => row.amountCents,
      formatter: (value) => `$${(Number(value) / 100).toFixed(2)}`
    },
    {
      id: 'tags',
      header: 'Tags',
      accessor: (row) => row.tags,
      formatter: (value) => (value as Array<string>).join(', ')
    }
  ]
});

accessor runs first to pull the raw value off the row (defaults to row[id] when omitted). formatter runs second to coerce the raw value into the string written to the file. The default formatter renders primitives verbatim, ISO-formats Dates, and JSON.stringifys objects/arrays.

Picking a format at runtime

const formats: Array<DataExportFormat> = ['csv', 'json', 'markdown', 'xlsx'];

return (
  <DropdownMenu>
    <DropdownMenuTrigger asChild>
      <Button disabled={isExporting}>Export…</Button>
    </DropdownMenuTrigger>
    <DropdownMenuContent>
      {formats.map((format) => (
        <DropdownMenuItem key={format} onSelect={() => exportData(rows, format)}>
          {format.toUpperCase()}
        </DropdownMenuItem>
      ))}
    </DropdownMenuContent>
  </DropdownMenu>
);

Awaiting the resolved file metadata

async function downloadAndToast() {
  const result = await exportData(rows, 'xlsx');

  if (!result) return; // server-side, no-op

  toast.success(`Saved ${result.fileName}`);
}

API Reference

Parameters

ParameterTypeDefaultDescription
optionsUseDataExportOptions<TData>—Hook configuration (see below). Required.

UseDataExportOptions<TData>

OptionTypeDefaultDescription
columnsArray<DataExportColumn<TData>>—Column projection. Order is preserved in the output. Required.
fileNamestring'export'File name without extension. The format-specific extension is appended automatically.
sheetNamestring'Sheet1'Sheet name used for xlsx exports.
csvBombooleantruePrepend a UTF-8 BOM to CSV output so Excel auto-detects encoding.

DataExportColumn<TData>

PropertyTypeDefaultDescription
idstring—Stable column id. Used as the JSON object key when format === 'json' and as the lookup key when accessor is omitted. Required.
headerstring—Header label rendered in CSV / Markdown / XLSX output. Required.
accessor(row: TData) => unknownrow => row[id]Pull the raw cell value for this column from a row.
formatter(value: unknown, row: TData) => stringdefault stringifierCoerce the raw value into the string written to the file. The default renders primitives verbatim, ISO-formats Dates, and JSON.stringifys objects/arrays.

Return Value — UseDataExportResult<TData>

PropertyTypeDescription
exportData(rows: Array<TData>, format: DataExportFormat) => Promise<DataExportResult | null>Project rows through columns and trigger a browser download in the requested format. Returns null on the server.
isExportingbooleantrue while a download is in flight (mainly relevant for the async xlsx path). Use it to disable the trigger button.

DataExportResult

PropertyTypeDescription
mimeTypestringMIME type written to the Blob.
extensionstringFile extension including the leading dot (e.g. '.csv').
fileNamestringFinal file name (<fileName><extension>).

Type Exports

TypeDescription
DataExportFormat'csv' | 'json' | 'markdown' | 'xlsx'
DataExportColumn<TData>Single-column definition (id, header, accessor, formatter).
UseDataExportOptions<TData>Hook input (columns, fileName, sheetName, csvBom).
UseDataExportResult<TData>Hook return value (exportData, isExporting).
DataExportResultFile metadata returned by exportData().

Format Reference

FormatExtensionMIME TypeNotes
csv.csvtext/csv;charset=utf-8RFC 4180 escaping. UTF-8 BOM prepended unless csvBom: false. CRLF line endings.
json.jsonapplication/json;charset=utf-8Pretty-printed (JSON.stringify(rows, null, 2)). Object keys come from each column's id.
markdown.mdtext/markdown;charset=utf-8GitHub-flavored table. |, \\, and newlines are escaped; multi-line cells use <br />.
xlsx.xlsxapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheetLazy-loads the xlsx package. Numbers, booleans, and Date values are written as their native Excel types.

Notes

  • The hook keeps no internal cache — pass the rows you want to export each time you call exportData(). This is what lets the same hook handle a filtered grid, a selection-only export, and a "download all" button.
  • Calling exportData() from a server component or during SSR is safe: the function bails out before touching document and resolves to null.
  • The download is triggered by injecting a temporary <a download> element. No popup, no new tab, no user gesture beyond the original click.

On this page