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