@uxf/data-grid
v12.4.0
Published
UXF DataGrid
Readme
@uxf/data-grid
A schema-driven, server-side data grid: one compound DataGrid component with a built-in toolbar, tabs, filters, sorting, column visibility, row selection, pagination and CSV export.
When to use
Reach for @uxf/data-grid when you have a paginated, filterable, server-fetched table driven by a generated schema (columns, filters, tabs). The grid owns fetching, filtering, sorting and pagination state; you provide a schema and a loader.
It is not a headless table primitive and not a spreadsheet — layout, toolbar and footer are opinionated. For a plain, fully custom table render the internal parts yourself (see Custom composition) rather than reaching for a different library.
Note: This package ships translations. Wrap your app in the
TranslationsProviderfrom@uxf/core-react/translationsfor correct labels.
Installation
yarn add @uxf/data-gridPeer dependencies (install if not already present):
yarn add @uxf/core @uxf/core-react @uxf/localize @uxf/styles @uxf/ui \
@dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities dayjs react react-domCSS
Import the dependency stylesheets first, then the grid's own stylesheet last. The @uxf/ui component styles are published under the flattened @uxf/ui/css/ directory (the @uxf/ui/<component>/<file>.css source paths do not exist in the published package).
@import url("tailwindcss/components.css");
@import url("@uxf/ui/css/button.css");
@import url("@uxf/ui/css/button-list.css");
@import url("@uxf/ui/css/calendar.css");
@import url("@uxf/ui/css/checkbox.css");
@import url("@uxf/ui/css/chip.css");
@import url("@uxf/ui/css/dialog.css");
@import url("@uxf/ui/css/dropdown.css");
@import url("@uxf/ui/css/icon.css");
@import url("@uxf/ui/css/label.css");
@import url("@uxf/ui/css/form-component.css");
@import url("@uxf/ui/css/input-basic.css");
@import url("@uxf/ui/css/input.css");
@import url("@uxf/ui/css/input-with-popover.css");
@import url("@uxf/ui/css/combobox.css");
@import url("@uxf/ui/css/modal-header.css");
@import url("@uxf/ui/css/multi-select.css");
@import url("@uxf/ui/css/multi-combobox.css");
@import url("@uxf/ui/css/pagination.css");
@import url("@uxf/ui/css/select.css");
@import url("@uxf/ui/css/toggle.css");
@import url("@uxf/ui/css/tabs.css");
/* must be after the component css files */
@import url("@uxf/data-grid/styles.css");Quick start
useDataGridControl owns grid state and actions; useDataGridFetching runs the loader whenever the request changes. Feed both into DataGrid.
"use client";
import { DataGrid } from "@uxf/data-grid";
import { useDataGridControl } from "@uxf/data-grid/use-data-grid-control";
import { useDataGridFetching } from "@uxf/data-grid/use-data-grid-fetching";
import { schema } from "@generated/data-grid/schema/example";
export function ExampleGrid() {
const { state, actions } = useDataGridControl({ schema });
const { isLoading, error, data, reload } = useDataGridFetching({ schema, state });
return (
<DataGrid
actions={actions}
data={data}
error={error}
isLoading={isLoading}
reload={reload}
schema={schema}
state={state}
/>
);
}useDataGridFetching uses a default loader that calls the conventional grid endpoint. To fetch yourself, pass a loader:
const { ... } = useDataGridFetching({
schema,
state,
loader: (gridName, request, encodedRequest) => myFetch(gridName, request),
});Persisting user config
useUserConfigLocalStorageAdapter returns a middleware (persists column visibility, widths, order, etc. to localStorage) and a useUserConfig hook that restores it. Wire the middleware into useDataGridControl and call useUserConfig(actions).
import { useDataGridControl } from "@uxf/data-grid/use-data-grid-control";
import { useUserConfigLocalStorageAdapter } from "@uxf/data-grid/user-config-storage-adapters/local-storage";
const { middleware, useUserConfig } = useUserConfigLocalStorageAdapter(schema);
const { state, actions } = useDataGridControl({
schema,
middleware,
// optional base64-encoded request or a Request object to hydrate initial state
initialState: encodedRequest,
// optional default column config
initialUserConfig: {
columns: {
id: { isHidden: true },
},
},
});
useUserConfig(actions);API
Only exports listed below are part of the public surface. Import paths resolve by filesystem (there is no exports map).
Root export — @uxf/data-grid
| Name | Kind | Description |
| ---------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------- |
| DataGrid | component | The compound grid. Props: DataGridProps<GridType>. |
| DataGridProps<GridType> | type | Grid props = base props + control (state, actions) + data (isLoading, error, data, reload). |
| InferDataGridRow<Schema> | type | Derives the row shape from a schema via ColumnTypes. |
| BaseGridType, Schema, ChangeTabFilterBehavior, DataGridActionCell, … | types | Re-exported from ./types. |
| mergeSchemaWithConfig(schema, config) | fn | Returns a new schema with a frontend config applied (see below). |
| encodeFilter(request) / decodeFilter(string) | fn | Base64 (de)serialize a grid request. |
Deep imports
| Import path | Export | Description |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| @uxf/data-grid/use-data-grid-control | useDataGridControl(config) | Owns grid state + actions. Config: { schema, initialState?, initialUserConfig?, middleware? }. |
| @uxf/data-grid/use-data-grid-fetching | useDataGridFetching(config) | Runs the loader on request changes. Config: { schema, state, loader?, isWithTabCounts? }. Returns { isLoading, error, data, reload }. |
| @uxf/data-grid/user-config-storage-adapters/local-storage | useUserConfigLocalStorageAdapter(schema), useClearLocalStorageUserConfig(schema) | localStorage persistence of user column config. |
| @uxf/data-grid/column-types | ColumnTypes (interface) | Augment to register custom column value types. |
| @uxf/data-grid/use-data-grid-filter-draft | useDataGridFilterDraft(control) | Holds filter edits aside until apply() is called. Returns { state, actions, apply } in the useDataGridControl shape. |
Notable optional DataGrid props: actionCell, bodyCells, changeTabFilterBehavior, customActions, filterApplyMode, filterHandlers, fulltextInputPlaceholder, getCsvDownloadUrl, hasStickyHeader, isRowsSelectable, isRowSelectDisabled, isWithTabCounts, keyExtractor, rowAccent, rowClassName, rowHeight, tabsVariant, HiddenColumnsComponent, NoRowsFallback, SelectedRowsToolbarActions, isDebug.
Column types
A column's type (from the schema) maps to a value type via the ColumnTypes interface, which drives InferDataGridRow. Built-in types:
boolean, chip, chips, date, datetime, email, id, int, money, phone, string, url, uuid.
Register a custom column type with module augmentation. Create a column-types.d.ts at the project root (next to tsconfig.json) — it must be an interface:
declare module "@uxf/data-grid/column-types" {
export interface ColumnTypes {
"my-custom-type": MyCustomType;
}
}Then infer the row type from a schema:
import { InferDataGridRow } from "@uxf/data-grid";
import { schema } from "@generated/data-grid/schema/example";
type Row = InferDataGridRow<typeof schema>;Filter handlers
Each filter is rendered by looking up filterHandlers[filter.type]. The grid uses defaultFilterHandlers unless you pass a filterHandlers prop. Built-in handler keys:
| filter.type | Renders |
| ------------------- | ------------------------------------------ |
| checkbox | Checkbox (value is sent only when checked) |
| date | Date range (from / to date pickers) |
| datetime | Datetime range |
| entitySelect | Single-select combobox (async entity) |
| entityMultiSelect | Multi-select combobox (async entity) |
| interval | Numeric range (min / max inputs) |
| multiSelect | Multi-select |
| select | Select |
| string | Text input |
Extend or override by merging into defaultFilterHandlers and passing the result as the filterHandlers prop:
import { defaultFilterHandlers } from "@uxf/data-grid/filter-handler";
<DataGrid filterHandlers={{ ...defaultFilterHandlers, myType: myHandler }} {...rest} />;Applying filters
By default a filter reaches the grid as soon as its value changes, in the filters drawer and anywhere
else the filters are rendered. filterApplyMode="confirm" changes that for the drawer: edits are held
back until the user presses the confirm button, and closing the drawer (the close button, ESC or a
click outside) discards them, leaving the grid at the last confirmed state.
<DataGrid filterApplyMode="confirm" {...rest} />Only the filters are deferred — fulltext, sorting, tabs and paging stay immediate, and so do the close
buttons on the filter chips below the toolbar. The clear-all button in the drawer header follows the
mode: in confirm it empties the drawer only, leaving the grid untouched until the filters are
confirmed.
For a filters panel of your own, outside the drawer, useDataGridFilterDraft gives the same behaviour:
const draft = useDataGridFilterDraft({ state, actions });
<DataGridFilters actions={draft.actions} filterHandlers={handlers} schema={schema} state={draft.state} />
<Button onClick={draft.apply}>Apply</Button>;The draft is seeded when the hook mounts and re-seeded whenever the applied filters change elsewhere, so unconfirmed edits are dropped when a filter chip is closed or a tab is switched.
Frontend config
mergeSchemaWithConfig returns a new schema with per-column and per-filter overrides and perPage applied — useful for tweaking a generated schema without regenerating it.
import { DataGrid } from "@uxf/data-grid";
import { mergeSchemaWithConfig } from "@uxf/data-grid";
import { useDataGridControl } from "@uxf/data-grid/use-data-grid-control";
import { useDataGridFetching } from "@uxf/data-grid/use-data-grid-fetching";
import { schema as baseSchema } from "@generated/data-grid/schema/example";
const schema = mergeSchemaWithConfig(baseSchema, {
perPage: 100,
columns: {
id: { width: 100, isHidden: true },
},
filters: {
id: { placeholder: "Search by ID..." },
},
});
export function ConfiguredGrid() {
const { state, actions } = useDataGridControl({ schema });
const { isLoading, error, data, reload } = useDataGridFetching({ schema, state });
return (
<DataGrid
actions={actions}
data={data}
error={error}
isLoading={isLoading}
reload={reload}
schema={schema}
state={state}
/>
);
}Custom composition
The DataGrid component is a fixed composition of internal parts. For a fully custom layout, import the parts individually and assemble them yourself, still driving them from useDataGridControl / useDataGridFetching:
@uxf/data-grid/root, @uxf/data-grid/toolbar, @uxf/data-grid/toolbar-tabs, @uxf/data-grid/toolbar-control, @uxf/data-grid/toolbar-customs, @uxf/data-grid/filter-list, @uxf/data-grid/table-v2, @uxf/data-grid/footer, @uxf/data-grid/pagination, @uxf/data-grid/row-counts, @uxf/data-grid/rows-per-page-select, @uxf/data-grid/selected-rows-toolbar, @uxf/data-grid/linear-progress, @uxf/data-grid/body-cell, @uxf/data-grid/filter-handler.
See data-grid.tsx (the default composition) and data-grid-custom-example.stories.tsx in the package for a worked example.
Gotchas
- Client component only. The hooks use React state, effects and
localStorage. Render the grid in a client component ("use client"). TranslationsProviderrequired. Labels come from bundled translations via@uxf/core-react/translations.- CSS order matters. Import
@uxf/data-grid/styles.cssafter the@uxf/ui/css/*component styles. ColumnTypesaugmentation must be aninterface, not atype, or the declaration merge will not apply.initialStateis a base64-encoded request string (or aRequestobject) — useencodeFilter/decodeFilterto (de)serialize, e.g. to persist the current view in the URL.isDebugis aDataGridprop, not auseDataGridControlconfig option.
