TypeScript2 min read
TypeScript Discriminated Unions
Type-safe state handling with a shared property that narrows the type
S
Shahar Amir
The Pattern
A discriminated union uses a common property to differentiate types.
typescript
1234
type Result<T> = | { status: 'loading' } | { status: 'success'; data: T } | { status: 'error'; error: Error };Why It's Powerful
TypeScript narrows the type automatically:
typescript
123456789101112131415
function handleResult(result: Result<User>) { switch (result.status) { case 'loading': // TypeScript knows: no data, no error return <Spinner />; case 'success': // TypeScript knows: result.data exists return <UserCard user={result.data} />; case 'error': // TypeScript knows: result.error exists return <ErrorMsg error={result.error} />; }}Real World: API Response
typescript
1234567891011121314151617181920
type ApiResponse<T> = | { ok: true; data: T } | { ok: false; error: string };
async function fetchUser(id: string): Promise<ApiResponse<User>> { try { const user = await api.getUser(id); return { ok: true, data: user }; } catch (e) { return { ok: false, error: e.message }; }}
// Usage - TypeScript enforces the checkconst result = await fetchUser('123');if (result.ok) { console.log(result.data.name); // ✅ Safe} else { console.log(result.error); // ✅ Safe}Real World: Form State
typescript
123456789101112131415
type FormState = | { step: 'info'; name: string; email: string } | { step: 'payment'; cardNumber: string } | { step: 'confirm'; orderId: string };
function FormStep({ state }: { state: FormState }) { switch (state.step) { case 'info': return <InfoForm name={state.name} />; case 'payment': return <PaymentForm card={state.cardNumber} />; case 'confirm': return <Confirmation id={state.orderId} />; }}The Discriminator
The shared property (status, ok, step) is called the discriminator.
Pick descriptive names. Your future self will thank you.
#unions#types#patterns#type-safety
Stay Updated 📬
Get the latest tips and tutorials delivered to your inbox. No spam, unsubscribe anytime.