Guides

Dynamic / backend-driven filters

Build the config map at runtime for faceted search and backend-described filters.

A config is just data — it doesn't have to be a static literal. When the backend describes which filters exist (e-commerce faceted search, a report builder), build the map at runtime and pass it in. The demo below builds its two filters from a FACETS array:

/products
params
{
  "brand": null,
  "color": null,
  "page": 1,
  "per_page": 10
}

Map your data to a config

Turn the backend's description into a key → f.*() map, and memoize it on the source data so filters stays referentially stable between renders:

function ProductFilters() {
  const { data: facets } = useQuery(facetsQueryOptions());

  const configs = useMemo(
    () =>
      Object.fromEntries(
        (facets ?? []).map((facet) => {
          switch (facet.type) {
            case 'checkbox':
              return [
                facet.key,
                f.multiSelect({
                  label: facet.label,
                  valueType: 'string',
                  options: facet.values.map((v) => ({
                    label: v.label,
                    value: v.value,
                    count: v.count // facet counts are first-class on FilterOption
                  }))
                })
              ];
            case 'range':
              return [facet.key, f.numberRange({ label: facet.label })];
            case 'toggle':
              return [facet.key, f.boolean({ label: facet.label })];
            default:
              return [facet.key, f.text({ label: facet.label })];
          }
        })
      ),
    [facets]
  );

  const { params, filters } = useFilters(configs);
  // render `filters`, fetch with `params` — same as static filters
}

Why it works

The hook subscribes to the URL once — a single internal call with a parser map, not one hook per filter — so a changing number of filters is fine. The only thing you give up is compile-time typing of params: you can't pass useFilters<P> for a shape unknown until runtime, so params comes back as a loose record, which is exactly what you feed your query anyway.

select / multiSelect require valueType up front — exactly the kind of static declaration a backend-driven config needs, since the options themselves (and their value type) may not be known until the facets have loaded.

On this page