Discriminated union state machines help you manage different states clearly and safely in your program. They make it easy to know what state you are in and what actions you can do next.
Discriminated union state machines in Typescript
type State =
| { type: 'idle' }
| { type: 'loading' }
| { type: 'success'; data: string }
| { type: 'error'; message: string };
function handleState(state: State) {
switch (state.type) {
case 'idle':
// handle idle
break;
case 'loading':
// handle loading
break;
case 'success':
// handle success with state.data
break;
case 'error':
// handle error with state.message
break;
}
}The type property is the discriminant that tells TypeScript which state it is.
Each state can have its own extra properties to hold data related to that state.
type LightState =
| { type: 'red' }
| { type: 'yellow' }
| { type: 'green' };type AuthState =
| { type: 'loggedOut' }
| { type: 'loggingIn' }
| { type: 'loggedIn'; user: string };
function printState(state: AuthState) {
switch (state.type) {
case 'loggedOut':
console.log('User is logged out');
break;
case 'loggingIn':
console.log('User is logging in');
break;
case 'loggedIn':
console.log(`Welcome, ${state.user}`);
break;
}
}This program models a door that can be closed, opening, open, or closing. It moves through states in order and prints each state.
type DoorState =
| { type: 'closed' }
| { type: 'opening' }
| { type: 'open' }
| { type: 'closing' };
function nextState(state: DoorState): DoorState {
switch (state.type) {
case 'closed':
return { type: 'opening' };
case 'opening':
return { type: 'open' };
case 'open':
return { type: 'closing' };
case 'closing':
return { type: 'closed' };
}
}
let state: DoorState = { type: 'closed' };
console.log(`Initial state: ${state.type}`);
state = nextState(state);
console.log(`Next state: ${state.type}`);
state = nextState(state);
console.log(`Next state: ${state.type}`);
state = nextState(state);
console.log(`Next state: ${state.type}`);
state = nextState(state);
console.log(`Next state: ${state.type}`);Always use a unique type string for each state to help TypeScript distinguish them.
Using a switch statement on the discriminant helps TypeScript know which properties are available.
This pattern helps prevent bugs by making invalid states or transitions impossible to represent.
Discriminated unions let you define clear, safe states with a common type property.
Use them to build simple state machines that are easy to understand and maintain.
TypeScript helps catch mistakes by knowing exactly which state you are working with.