Skip to content

Remote Data

Seizen Table supports a Remote Mode that enables seamless integration with external data sources. In this mode, filtering, sorting, and pagination are handled by your external data source (e.g., REST API, GraphQL, TanStack DB), while Seizen Table manages the UI state.

Enable Remote Mode by adding the remote option to useSeizenTable:

import { useSeizenTable, SeizenTable } from "@izumisy/seizen-table";
// Without pagination
const table = useSeizenTable({
data: filteredUsers, // Pre-filtered data from external source
columns,
remote: true,
});
// With pagination (requires totalRowCount for page calculation)
const table = useSeizenTable({
data: filteredUsers,
columns,
remote: { totalRowCount: 1000 },
});

If you want a small helper to manage remote-related state (data/loading/error/totalCount/cursors), use useRemoteData from @izumisy/seizen-table-plugins/remote.

import { useSeizenTable, SeizenTable } from "@izumisy/seizen-table";
import { useRemoteData } from "@izumisy/seizen-table-plugins/remote";
const remote = useRemoteData<User>();
const table = useSeizenTable({
data: remote.data,
columns,
remote: remote.getRemoteOptions(),
});
return <SeizenTable table={table} loading={remote.loading} />;

useRemoteData is an optional helper hook that manages only data-related state for Remote Mode. It does not manage table UI state (pagination/sorting/filters); those are still owned by useSeizenTable.

  • Data state: data, loading, error
  • Server information: totalCount (used to calculate page count)
  • Cursor utilities: getCursor(pageIndex), clearCursors() for cursor-based pagination
  • Remote option helper: getRemoteOptions() to feed remote: true / remote: { totalRowCount } appropriately
  • Keeps responsibilities separated: table state vs. network/data state
  • Avoids repeating the same useState bundle across screens/tables
  • Makes cursor-based pagination easier to implement consistently
  • Gives a single, predictable place to set loading/error and the server totalCount
ModeFiltering/Sorting/Pagination
DefaultTanStack Table processes data internally
RemoteExternal source processes data; UI state is synchronized

In Remote Mode:

  1. User interacts with filter/sort/pagination controls
  2. Internal state updates (for UI synchronization)
  3. Events are emitted (filter-change, sorting-change, pagination-change)
  4. Your app subscribes to events and updates the external data source
  5. New data prop is passed to the table

In many remote setups, a sorting/filter change should reset pagination to the first page. If you use cursor-based pagination, you usually also want to clear cursors at the same time:

remote.clearCursors();
table.setPageIndex(0);

Use the loading prop to show a loading overlay while fetching data:

<SeizenTable
table={table}
loading={isLoading}
/>
// With custom loader
<SeizenTable
table={table}
loading={isLoading}
loaderComponent={<MyCustomSpinner />}
/>

Customize the loading overlay appearance:

:root {
--szui-loading-overlay-bg: rgba(255, 255, 255, 0.8);
--szui-spinner-size: 32px;
--szui-spinner-color: #3b82f6;
}

Here’s a complete example integrating with a REST API using useRemoteData:

import { useCallback, useEffect } from "react";
import {
useSeizenTable,
useSeizenTableEvent,
SeizenTable,
} from "@izumisy/seizen-table";
import { FilterPlugin } from "@izumisy/seizen-table-plugins/filter";
import { useRemoteData } from "@izumisy/seizen-table-plugins/remote";
function UsersTable() {
const remote = useRemoteData<User>();
const table = useSeizenTable({
data: remote.data,
columns,
plugins: [FilterPlugin.configure({ disableGlobalSearch: true })],
remote: remote.getRemoteOptions(),
});
const resetRemotePagination = useCallback(() => {
remote.clearCursors();
table.setPageIndex(0);
}, [remote, table]);
const fetchUsers = useCallback(
async (
pagination: { pageIndex: number; pageSize: number },
sorting: { id: string; desc: boolean }[],
filters: { id: string; value: unknown }[]
) => {
remote.setLoading(true);
remote.setError(null);
try {
const query = new URLSearchParams();
// Convert filter state to query params (example)
filters.forEach((f) => {
const { operator, value } = f.value as {
operator: string;
value: string;
};
query.append(`filter[${f.id}][${operator}]`, value);
});
// Add sorting
sorting.forEach((s) => {
query.append("sort", `${s.desc ? "-" : ""}${s.id}`);
});
// Add pagination
query.append("page", String(pagination.pageIndex + 1));
query.append("limit", String(pagination.pageSize));
// Optional: cursor-based pagination
const cursor = remote.getCursor(pagination.pageIndex - 1);
if (cursor) query.append("cursor", cursor);
const res = await fetch(`/api/users?${query}`);
if (!res.ok) throw new Error(`Request failed: ${res.status}`);
const json = await res.json();
remote.setData(json.items, {
totalCount: json.total,
cursor: json.nextCursor,
});
} catch (e) {
remote.setError(e instanceof Error ? e : new Error("Fetch failed"));
remote.setData([], { totalCount: 0 });
} finally {
remote.setLoading(false);
}
},
[remote]
);
// Fetch data on pagination change
useSeizenTableEvent(table, "pagination-change", (pagination) => {
fetchUsers(pagination, table.getSortingState(), table.getFilterState());
});
// Sorting changes usually reset to the first page
useSeizenTableEvent(table, "sorting-change", (sorting) => {
resetRemotePagination();
fetchUsers(
{ pageIndex: 0, pageSize: table.getPaginationState().pageSize },
sorting,
table.getFilterState()
);
});
// Filter changes usually reset to the first page
useSeizenTableEvent(table, "filter-change", (filters) => {
resetRemotePagination();
fetchUsers(
{ pageIndex: 0, pageSize: table.getPaginationState().pageSize },
table.getSortingState(),
filters
);
});
// Initial fetch
useEffect(() => {
fetchUsers(
table.getPaginationState(),
table.getSortingState(),
table.getFilterState()
);
}, [fetchUsers, table]);
return <SeizenTable table={table} loading={remote.loading} />;
}

Plugins can check if Remote Mode is enabled via table.remote:

function MyPluginComponent() {
const { table } = usePluginContext();
if (table.remote) {
// Adjust behavior for Remote Mode
// e.g., hide global search
return null;
}
return <GlobalSearchInput />;
}