Model screen state as a discriminated union
TypeScript
// Every async view state is one variant, so impossible states cannot be built.
type ViewState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; message: string; retryable: boolean };
function assertNever(value: never): never {
throw new Error(`unhandled state: ${JSON.stringify(value)}`);
}
function labelFor<T>(state: ViewState<T>): string {
switch (state.status) {
case 'idle':
return 'Not loaded yet';
case 'loading':
return 'Loading...';
case 'success':
return 'Data ready';
case 'error':
return state.retryable ? `Retry: ${state.message}` : `Failed: ${state.message}`;
default:
return assertNever(state);
}
}
const history: ViewState<string[]>[] = [
{ status: 'idle' },
{ status: 'success', data: ['alpha', 'beta'] },
{ status: 'error', message: 'timeout', retryable: true },
];
console.log(history.map(labelFor));