skip to content
lav.ren / tinker / het-bolwerk-filter

A React program filter for a cultural center

Faceted filtering over a cultural center's full program — dates, disciplines, venues, age groups — with URL-synced state so people can share filtered views. Built for Het Bolwerk during my time at Volta.

Date
2022
Stack
ReactDrupalJavaScript

Het Bolwerk is a cultural center in Vilvoorde that publishes a few hundred events a season across podium, music, expo, family, and workshops. The existing Drupal listing was a flat paginated page with one taxonomy dropdown — fine for a blog, hostile for someone trying to find “a family show on a Saturday in March.” This experiment is the React island I built to replace it, still live on the podium page.

The site itself is Drupal + Twig + Tailwind. Design by Volta.

What the filter actually does

Five facets, all combinable:

  • Date range — from/to, with sensible defaults that bias toward “soon”
  • Discipline — podium, music, expo, family, workshops (multi-select)
  • Venue — the center has multiple rooms; events can also be off-site
  • Age — family programming has age bands; the filter resolves “my kid is 7” to the right overlap across 5+, 6+, and all ages entries
  • Free text — matches title and artist, not the full description, so typing “bach” doesn’t pull up every show that mentions Bach in the blurb

Every state change pushes to the URL with history.replaceState, so the browser’s back button doesn’t get clobbered but the URL remains shareable. A teacher can filter to “family + Saturdays + March” and paste the URL into a newsletter.

Where the interesting work lives

The Drupal backend exposes events as a JSON endpoint with every facet already resolved server-side — taxonomy IDs mapped to labels, age bands normalized, dates as ISO strings. That means the React side does no lookups at all: it filters an in-memory array and re-renders. For a few hundred events, that’s instant on any device. No virtualization, no windowing, no debounced API calls — just a plain .filter() chain and useMemo to cache the sorted output.

The tradeoff: full dataset ships on first load (~80 KB gzipped at the time). For a program with ten thousand events that’d be wrong; for a few hundred it’s a rounding error against the hero image.

Lessons worth keeping

  • Taxonomy work is editorial work. The single biggest win wasn’t code — it was sitting with the program team and flattening their internal genre tags (which had grown to forty-something over years) into five disciplines people actually use.
  • URL state beats local state for anything a user might share. The newsletter team asked for “a way to link to a filtered view” a month after launch. We already had it for free.
  • Date ranges are always the trickiest facet. Timezones, inclusive vs exclusive bounds, “this weekend” shortcuts — expect to spend more time here than on anything else.