Skip to Content
Getting StartedState Reducer Example

Last Updated: 3/18/2026


State Reducer Example

This example demonstrates how to use TS-Pattern to create a type-safe state reducer for a frontend application that fetches data.

The Scenario

Our application can be in four different states: idle, loading, success and error. Depending on which state we are in, some events can occur. Here are all the possible types of event our application can respond to: fetch, success, error and cancel.

Type Definitions

type State = | { status: 'idle' } | { status: 'loading'; startTime: number } | { status: 'success'; data: string } | { status: 'error'; error: Error }; type Event = | { type: 'fetch' } | { type: 'success'; data: string } | { type: 'error'; error: Error } | { type: 'cancel' };

The Reducer

Even though our application can handle 4 events, only a subset of these events make sense for each given state. For instance we can only cancel a request if we are currently in the loading state.

Instead of writing nested switch statements, we can use pattern matching to simultaneously check the state and the event object:

import { match, P } from 'ts-pattern'; const reducer = (state: State, event: Event) => match([state, event]) .returnType<State>() .with( [{ status: 'loading' }, { type: 'success' }], ([_, event]) => ({ status: 'success', data: event.data }) ) .with( [{ status: 'loading' }, { type: 'error', error: P.select() }], (error) => ({ status: 'error', error }) ) .with( [{ status: P.not('loading') }, { type: 'fetch' }], () => ({ status: 'loading', startTime: Date.now() }) ) .with( [ { status: 'loading', startTime: P.when((t) => t + 2000 < Date.now()), }, { type: 'cancel' }, ], () => ({ status: 'idle' }) ) .with(P._, () => state) .exhaustive();

Key Concepts Used

  1. Multiple patterns: Matching on [state, event] tuples
  2. .returnType<State>(): Enforces that all branches return a valid State
  3. P.select(): Extracts and injects specific values
  4. P.not(): Matches everything but a specific value
  5. P.when(): Uses guard functions for custom conditions
  6. P._: Wildcard that matches any value
  7. .exhaustive(): Ensures all cases are handled at compile time