patterntypescriptModeratepending
TypeScript discriminated unions for type-safe state machines
Viewed 0 times
discriminated uniontagged unionstate machinenarrowingexhaustive check
Problem
Need to model different states with different data shapes in a type-safe way.
Solution
Use discriminated unions with a shared literal type field:
// Define states with discriminant field
type RequestState =
| { status: 'idle' }
| { status: 'loading'; startedAt: number }
| { status: 'success'; data: User[]; fetchedAt: number }
| { status: 'error'; error: Error; retryCount: number };
// TypeScript narrows the type based on discriminant
function renderState(state: RequestState): string {
switch (state.status) {
case 'idle':
return 'Ready to fetch';
case 'loading':
return `Loading since ${state.startedAt}`; // startedAt available
case 'success':
return `Got ${state.data.length} users`; // data available
case 'error':
return `Error: ${state.error.message}`; // error available
}
}
// Exhaustiveness checking
function assertNever(x: never): never {
throw new Error(`Unexpected: ${x}`);
}
// Transitions as functions
function transition(state: RequestState, action: Action): RequestState {
switch (action.type) {
case 'FETCH':
return { status: 'loading', startedAt: Date.now() };
case 'SUCCESS':
return { status: 'success', data: action.data, fetchedAt: Date.now() };
case 'ERROR':
if (state.status === 'loading') {
return { status: 'error', error: action.error, retryCount: 0 };
}
return state; // Can only error from loading
default:
return assertNever(action);
}
}
// Real-world: Form field validation
type FieldState =
| { valid: true; value: string }
| { valid: false; value: string; errors: string[] };
function validate(input: string): FieldState {
const errors: string[] = [];
if (input.length < 3) errors.push('Too short');
if (!/^[a-z]+$/i.test(input)) errors.push('Letters only');
return errors.length === 0
? { valid: true, value: input }
: { valid: false, value: input, errors };
}Why
Discriminated unions encode state-dependent data in the type system. The compiler ensures you handle all states and only access fields that exist for each state.
Context
TypeScript applications modeling complex state
Revisions (0)
No revisions yet.