Tulip Logo IconTulip
Data Tables

Strategies

Choose between server-first and local-first data-table loading strategies.

Data-table strategies keep table configuration separate from loading behavior.

Use strategies from the client entrypoint:

import {
  createTableConfig,
  TableConfigProvider,
  useInfiniteStrategy,
  useLocalStrategy,
  usePaginationStrategy,
} from "@tulip-systems/data-tables/client";

Local strategy

Use useLocalStrategy() when rows already live in client state or a local collection and the table only needs configuration, sorting state, command integration, and rendering.

"use client";

import type { TableColumnDef } from "@tulip-systems/data-tables";
import {
  createTableConfig,
  TableConfigProvider,
  useLocalStrategy,
} from "@tulip-systems/data-tables/client";

function CustomerLocalTableProvider({
  data,
  columns,
  children,
}: {
  data: { id: string; name: string }[];
  columns: TableColumnDef<{ id: string; name: string }>[];
  children: React.ReactNode;
}) {
  const strategy = useLocalStrategy({ total: data.length });

  const config = createTableConfig({
    queryData: data,
    columns,
    getRowId: (row) => row.id,
    strategy,
  });

  return <TableConfigProvider config={config}>{children}</TableConfigProvider>;
}

Server-first strategies

Use pagination or infinite strategies when the server remains the source of truth for the current result set.

  • pagination works best for admin screens with explicit pages and totals
  • infinite loading works best for feed-like or browser-like screens
  • local works best when the working set is already loaded or managed by TanStack DB

Keep Query Strategy-Agnostic

Define filters with defineFilters(...) and descriptor builders. The table strategy decides how rows are loaded, but the query definition should stay portable.

import { defineFilters, stringFilter } from "@tulip-systems/query";

export const filters = defineFilters({
  search: stringFilter.build((value) =>
    value ? { field: "search", operator: "ilike", value } : null,
  ),
});

This lets the same module support a suspense query, a paginated table, an infinite table, or a local on-demand collection without creating a second filter API.

On this page