Note

← Back to notes

Applied Query State vs Typing State

A practical state model for search, filters, dashboards, and data-heavy product surfaces.

State modeling Filters Search Frontend architecture

The problem

Complex UIs often have fast-changing input, but the product should not treat every keystroke as a committed query. A user editing a filter is not always asking the system to fetch new data yet.

This shows up in dashboards, admin tools, and resource explorers: a search input, a date range control, a resource list filter, URL query params, and cache keys may all be connected. If every draft edit immediately becomes the source of truth, request behavior gets noisy quickly.

  • dashboard filters
  • search input
  • date range controls
  • resource list filtering
  • URL query params
  • cache keys

The pattern

I usually separate the state the user is editing from the state the system has agreed to run. The names can vary, but the boundary matters.

Typing state

  • local or draft state
  • changes on every keystroke
  • optimized for editing and validation

Applied query state

  • committed state
  • drives fetching, caching, URL sync, pagination, and table/chart rendering
  • changes on submit, Enter, Apply, or debounced commit depending on UX needs

Applied query state should usually change on submit, Enter, Apply, or a deliberate debounced commit. The right trigger depends on the product, but it should be a product decision rather than an accident of input binding.

Why it matters

This separation makes request behavior predictable. The UI can respond instantly while the data layer stays tied to a committed query shape.

  • avoids refetching on every keystroke
  • reduces stale request problems
  • keeps cache keys stable
  • makes loading and empty states easier to reason about
  • avoids resetting pagination or charts while the user is still editing

Trade-offs

The trade-off is extra state and a slightly more explicit mental model. For simple forms, a single state value is often enough. For dashboards, expensive queries, and data-heavy views, the separation usually pays for itself.

  • Use one state for simple local forms.
  • Use draft + applied query state when fetching, caching, URL sync, pagination, or chart rendering depends on the query.
  • Debounce can reduce request frequency, but it does not replace the boundary between editing state and committed query state.

Implementation shape

The implementation does not need to be elaborate. The important part is naming the handoff from editing state to committed query state.

draftSearch = input value
appliedQuery = committed query used for fetching

onChange -> update draftSearch
onSubmit -> update appliedQuery, reset page, trigger fetch/cache lookup

The takeaway

Typing state is what the user is editing; applied query state is what the system has agreed to run.

Related case

Dashboard Data Contracts

This same separation is useful when dashboard widgets depend on stable filters, cache keys, and render boundaries.