Skip to main content

Table State Management

TanStack Table

TanStack Table (formerly React Table) manages table state including:

  • Column filters
  • Sorting
  • Pagination
  • Advanced filters
  • Date filters

Table Context Pattern

Location: src/components/tables/

Structure:

tables/
├── common/
│ ├── useFetchTableData.tsx # Data fetching hook
│ ├── tables.ts # Type definitions
│ └── ...
├── petitions/
│ ├── usePetitionsContext.tsx # Petitions table context
│ └── PetitionTable.tsx
└── ...

Implementation

1. Table Context Provider

// components/tables/petitions/usePetitionsContext.tsx
"use client";
import { createContext, useContext } from "react";
import { useFetchTableData } from "../common/useFetchTableData";

const PetitionsContext = createContext<ContextReturnType<any> | undefined>(undefined);

const petitionsTableConfig: TableConfig = {
tableName: "petitions",
selectQuery: `
*,
circulator!inner (*),
createdBy!inner (first_name, last_name, email)
`,
defaultSorting: [{ id: "createdAt", desc: true }],
};

export function PetitionsContextProvider({
children,
tableState,
}: ProviderProps) {
const params = useParams<{ slug: string }>();
const campaignId = params?.slug;

const tableData = useFetchTableData({
config: petitionsTableConfig,
campaignId,
tableState,
});

return (
<PetitionsContext.Provider value={tableData}>
{children}
</PetitionsContext.Provider>
);
}

export function usePetitionsContext() {
const context = useContext(PetitionsContext);
if (!context) {
throw new Error("usePetitionsContext must be used within a PetitionsContextProvider");
}
return context;
}

2. Table Data Hook

// components/tables/common/useFetchTableData.tsx
"use client";
import { useState, useEffect, useCallback, useRef } from "react";
import { createClient } from "@/lib/supabase/client";

export function useFetchTableData({
config,
campaignId,
tableState,
}: UseTableDataOptions) {
const [data, setData] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [count, setCount] = useState(0);
const cache = useRef<Record<string, { data: any[]; count: number }>>({});

const fetchData = useCallback(async (force = false) => {
const cacheKey = generateCacheKey(
tableState.columnFilters,
tableState.sorting,
tableState.pagination
);

// Check cache first
if (!force && cache.current[cacheKey]) {
setData(cache.current[cacheKey].data);
setCount(cache.current[cacheKey].count);
return;
}

setIsLoading(true);
const supabase = createClient();

let query = supabase
.from(config.tableName)
.select(config.selectQuery, { count: "exact" });

// Apply filters, sorting, pagination
query = applyColumnFilters(query, tableState.columnFilters);
query = applySorting(query, tableState.sorting);
query = applyPagination(query, tableState.pagination);

const { data: fetchedData, error, count } = await query;

if (!error && fetchedData) {
cache.current[cacheKey] = { data: fetchedData, count };
setData(fetchedData);
setCount(count || 0);
}

setIsLoading(false);
}, [config, campaignId, tableState]);

useEffect(() => {
fetchData();
}, [fetchData]);

return {
data,
isLoading,
count,
refetch: () => fetchData(true),
// TanStack Table state
columnFilters: tableState.columnFilters,
setColumnFilters: tableState.setColumnFilters,
sorting: tableState.sorting,
setSorting: tableState.setSorting,
pagination: tableState.pagination,
setPagination: tableState.setPagination,
};
}

3. Table Component

// components/tables/petitions/PetitionTable.tsx
"use client";
import { usePetitionsContext } from "./usePetitionsContext";
import { useReactTable, getCoreRowModel, getFilteredRowModel } from "@tanstack/react-table";

export function PetitionTable() {
const {
data,
isLoading,
columnFilters,
setColumnFilters,
sorting,
setSorting,
pagination,
setPagination,
} = usePetitionsContext();

const table = useReactTable({
data,
columns: petitionColumns,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
pagination,
columnPinning,
},
onColumnFiltersChange: setColumnFilters,
onSortingChange: setSorting,
onPaginationChange: setPagination,
onColumnPinningChange: setColumnPinning,
manualFiltering: true, // Server-side filtering
manualSorting: true, // Server-side sorting
manualPagination: true, // Server-side pagination
});

return (
<div>
<Table>
{/* Table rows */}
</Table>
<Pagination table={table} />
</div>
);
}

Key Features:

  • Client-side caching - Caches query results
  • Server-side operations - Filters, sorting, pagination on server
  • URL synchronization - Table state synced with URL params
  • Type-safe - Full TypeScript support
  • Automatic refetch - Refetches when filters/sorting/pagination changes