Advanced

State Persistence

Everything the user can rearrange goes into one versioned, JSON-serializable snapshot: column order, widths, visibility and pinning, plus density and whatever each feature decides to contribute.

Persisting to localStorage

Rearrange the grid below: drag a column, resize one, hide one from the chooser, sort, filter or change the page size. Then reload the page and it comes back the way you left it.

key: docs-datagrid-persistence

Loading the persisted layout...

nothing stored yet
<!-- Column layout, sort, filter, page size and density are mirrored into
     localStorage and restored before the first client paint. -->
<DataGrid {grid} persistState={{ key: 'orders-grid' }} />

<!-- With a migration for snapshots an older version of your app wrote.
     Return undefined to discard one you cannot upgrade: the grid then
     falls back to the column defaults rather than half-applying it. -->
<DataGrid
  {grid}
  persistState={{
    key: 'orders-grid',
    migrate: (stored) => (stored.version === 1 ? stored : undefined)
  }}
/>

Snapshots by Hand

getState and setState are what persistState calls. Because the snapshot is plain JSON, the same object can go to a server as a saved view, or into a URL as a shareable one.

The two are always on grid.api, whatever features are registered, because they belong to the kernel rather than to a feature. Sort and filter the grid, save, change your mind, then restore.

nothing saved yet
Country
Hoang Kowalski
Design
Poland
$127,691.00
Bruno Nguyen
Growth
Brazil
$105,146.00
Bruno Dubois
Design
Nigeria
$86,538.00
Farid Haddad
Growth
Brazil
$76,443.00
Jonas Yilmaz
Core
Vietnam
$129,812.00
24 rows
1–5 of 24
// The same thing by hand, which is what persistState calls.
const snapshot = grid.api.getState();
grid.api.setState(snapshot);

// The snapshot is versioned and JSON-serializable, so it travels to a
// server or into a URL just as well as into localStorage.
await fetch('/api/views', { method: 'POST', body: JSON.stringify(snapshot) });

// SNAPSHOT_VERSION is what a migrate hook checks against.
import { SNAPSHOT_VERSION } from '@sv5ui/datagrid';

What a Snapshot Holds

The object below is rebuilt as you work. Sort a column, filter, drag a column edge, move a column, hide one from the chooser, change the density: each lands in its own key. Identity is by id throughout, so a column or feature that disappeared is dropped on restore, and one added since keeps its defaults rather than inheriting someone else's.

Country
Hoang Kowalski
Design
Poland
$127,691.00
Bruno Nguyen
Growth
Brazil
$105,146.00
Bruno Dubois
Design
Nigeria
$86,538.00
Farid Haddad
Growth
Brazil
$76,443.00
Jonas Yilmaz
Core
Vietnam
$129,812.00
24 rows
1–5 of 24
{
  "version": 1,
  "features": {
    "pagination": 5
  }
}
// What a snapshot holds. Everything is optional except version: a grid
// with nothing rearranged writes almost nothing.
{
  version: 1,
  columns: {
    order: ['name', 'team', 'salary'],
    widths: { name: 240 },
    hidden: { country: true },
    pinned: { name: 'left' },
    collapsed: { pay: true }        // header groups, by group id
  },
  density: 'compact',
  features: {
    sorting: [{ columnId: 'salary', direction: 'desc' }],
    filtering: { quick: 'core', columns: {} },
    pagination: 25
  }
}

// Columns are keyed by id: an id that disappeared is dropped, an id added
// since keeps its defaults, and a column that appeared since the snapshot
// was written goes last rather than losing its place. Groups are keyed
// apart from columns, because a folded group is not a hidden column: what
// the chooser put away stays away when its group opens. Features are keyed by feature id, so one
// registered after the snapshot was written simply starts fresh.

What Is Deliberately Left Out

Only three features write a slice: sorting, filtering and pagination, and pagination stores the page size rather than the page. Selection and edits are not layout, so they are not in the snapshot at all.

Try it: sort a column and tick a row or two, save, then clear the sort and untick everything. Restoring brings the sort back and leaves the selection where you left it. A feature decides its own slice through serialize, so your own features join the snapshot the same way the built-ins do.

sort: unsorted 0 selected
Country
Hoang Kowalski
Design
Poland
$127,691.00
Bruno Nguyen
Growth
Brazil
$105,146.00
Bruno Dubois
Design
Nigeria
$86,538.00
Farid Haddad
Growth
Brazil
$76,443.00
Jonas Yilmaz
Core
Vietnam
$129,812.00
12 rows
1–5 of 12
// What a feature contributes is its own decision, through serialize.

// sorting     the sort array, when there is one
// filtering   the whole filter model, when anything is active
// pagination  the page size only

// The page number is deliberately absent: restoring page 7 of a list the
// user has since filtered lands them nowhere. The selection and the edit
// history are absent for the same reason, being about a session rather
// than about a view.

Rendering Client-Side

There is no grid to show here, because the point is the absence of one: on the server there is no localStorage, so a persisted grid rendered there paints its defaults and corrects itself after hydration, which is a visible flash. Gate the grid behind a mounted flag and it paints once.

This page keeps its own SSR and gates only the grid, which is the smaller hammer of the two. The first demo on this page is behind exactly this flag, and it currently reads server render, grid withheld .

// localStorage is a browser thing, so a server-rendered grid paints the
// defaults and corrects after hydration, which reads as a flash.

// Render a persisted grid client-side to avoid it:
export const ssr = false;

// Or gate the grid itself, which keeps the rest of the page server-rendered:
let mounted = $state(false);
$effect(() => { mounted = true; });

GridSnapshot

FieldDescription
versionSNAPSHOT_VERSION at the time it was written
columns.orderColumn ids in their current order
columns.widthsWidths a resize changed
columns.hiddenWhat the chooser hid
columns.pinnedPin sides
columns.collapsedFolded header groups, keyed by the group id rather than by column, since no column carries it
densityWritten only when it is not standard
featuresOne slice per feature, keyed by feature id

Feature Slices

What each built-in feature writes into snapshot.features.

FeatureContributes
sortingThe sort array, when there is one
filteringThe whole filter model, when anything is active. Values are stored in the row's own units, so a percent column restores the 0.05 behind a panel that read 5%, and a set filter holds JSON-safe keys, so the values that come back are the ones that went in
paginationThe page size only, never the page number
Everything elseSelection, editing history and row pinning stay out: they are about a session rather than a view