When we rebranded Zeiko as an AI business manager, we knew the humble data table had to keep up. Enterprise customers expect dashboards that filter thousands of records instantly, respond to AI-driven queries, and never lose state across views. This post walks through the tuning work we’ve done over the past sprint—work that your team can borrow for any large-scale table UI.
Because Zeiko and Lezgo share this table inside the same monorepo, every fix goes live in both properties immediately—see the SaaS experience at zeiko.io and the partner ecosystem at lezgo.co.
1. Real Query Keys, Real Refetches
Problem: deterministic fetches didn’t always fire. React Query would reuse cached payloads because our key missed subtle changes (e.g., new filters, sort toggles, or pagination).
Fix: we lifted the determinism into useDataTable:
- Every key is derived from
app, context, search, filters, sort, and pagination. - Hash-based
useEffect ensures we refetch when any driver changes—even if the object reference stays stable. - AI queries still skip deterministic refetches; cached AI results get invalidated only when filters & sort diverge from the cached snapshot.
Takeaway: never rely on JSON.stringify sprinkled across components. Centralize key generation and trigger refetches from the hook that owns the state.
2. Reducer-Driven State (No more useState spaghetti)
The table hook now uses a reducer:
const [state, dispatch] = useReducer(reducer, createDefaultState(defaultSort));
Benefits:
- Transition logic lives next to the state.
SET_SEARCH_TEXT, SUBMIT_SEARCH, SET_FILTERS, etc. are explicit. - Dispatch functions are referentially stable, so React Query dependencies stay tight.
- AI query state no longer fights the deterministic search box; we can preserve the “AI: …” badge while typing.
For teams migrating: start by codifying your current states (searchQuery, filters, sort, pagination). Then write a reducer that enforces the invariants you want (e.g., reset pagination on filter changes).
3. Auto-Hide Columns Without Flicker
Auto-hiding empty columns felt magic—until users sorted on untouched datasets. Sorting triggered the hide pass and collapsed every column.
What changed: autoHideEmptyColumnsMode now defaults to . We only hide columns after a filter is applied; pure sorting leaves the layout alone. You can opt back into when the UX warrants it.