Fetching data
Wire params into React Query, SWR, or any fetcher — and use it as the cache key.
params is designed to be passed straight to your data layer. It's a plain object
of the committed filter values plus pagination, and it changes exactly when the URL
does — which makes it a correct cache key.
React Query
Spread params into the query key and the fetch:
const { params } = useFilters(configs);
const { data } = useQuery({
queryKey: ['products', params],
queryFn: () => productApi.getAll(params)
});When any filter changes, params becomes a new object, the query key changes, and
React Query refetches and caches the new result — no useEffect, no manual
invalidation.
SWR
Same idea — params is the key:
const { params } = useFilters(configs);
const { data } = useSWR(['products', params], () => productApi.getAll(params));Plain fetch
If you fetch by hand, drive it off params in an effect:
const { params } = useFilters(configs);
useEffect(() => {
const controller = new AbortController();
productApi.getAll(params, { signal: controller.signal }).then(setRows);
return () => controller.abort();
}, [params]);A string cache key: paramsStr
When you'd rather key on a string than an object, paramsStr is params
serialized to a deterministic, sorted string — same state always produces the
same string, so it's a stable key or memo dependency:
const { params, paramsStr } = useFilters(configs);
const { data } = useQuery({ queryKey: [paramsStr], queryFn: () => productApi.getAll(params) });
// paramsStr → "page=1&per_page=10&search=acme&status=open"Keys are sorted, unset filters are dropped, and values are URL-encoded, so equivalent states collide and distinct ones don't.
A note on nulls
Unset filters are null in params (a filter with a
defaultValue is its default
instead — never null). Most JSON APIs and query-string serializers handle
null fine, but if yours can't, strip them before the request:
const clean = Object.fromEntries(Object.entries(params).filter(([, v]) => v != null));Deferred commits (Values & commits) mean a half-typed search
never lands in params — so you never fetch on every keystroke, even though the input updates
instantly.