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
- Multiple patterns: Matching on
[state, event]tuples .returnType<State>(): Enforces that all branches return a valid StateP.select(): Extracts and injects specific valuesP.not(): Matches everything but a specific valueP.when(): Uses guard functions for custom conditionsP._: Wildcard that matches any value.exhaustive(): Ensures all cases are handled at compile time