Last active
March 2, 2026 21:50
-
-
Save leovido/b0fd3b0c56ee04cffd547f2e44b30569 to your computer and use it in GitHub Desktop.
Parsing network request (discriminated union)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ❌ The classic anti-pattern: flags that can contradict each other | |
| type FetchStateBad = { | |
| isLoading: boolean; | |
| data: User | null; | |
| error: Error | null; | |
| // What does isLoading=true, data=someUser, error=someError mean? | |
| // This state is "impossible" but the type allows it | |
| }; | |
| // ✅ Parse into a discriminated union where every state is valid by construction | |
| type FetchState<T> = | |
| | { status: "idle" } | |
| | { status: "loading" } | |
| | { status: "success"; data: T } | |
| | { status: "error"; error: Error }; | |
| // State transitions are parsers: old state + event → new state | |
| // Illegal transitions simply cannot be expressed | |
| function transition<T>( | |
| state: FetchState<T>, | |
| event: | |
| | { type: "FETCH" } | |
| | { type: "SUCCESS"; data: T } | |
| | { type: "ERROR"; error: Error } | |
| ): FetchState<T> { | |
| switch (state.status) { | |
| case "idle": | |
| case "error": | |
| if (event.type === "FETCH") return { status: "loading" }; | |
| return state; | |
| case "loading": | |
| if (event.type === "SUCCESS") return { status: "success", data: event.data }; | |
| if (event.type === "ERROR") return { status: "error", error: event.error }; | |
| return state; | |
| case "success": | |
| if (event.type === "FETCH") return { status: "loading" }; | |
| return state; | |
| } | |
| } | |
| // Consumption is exhaustive — TypeScript forces you to handle every case | |
| function render<T>(state: FetchState<T>) { | |
| switch (state.status) { | |
| case "idle": return <Idle />; | |
| case "loading": return <Spinner />; | |
| case "success": return <DataView data={state.data} />; // data is T, not T | null | |
| case "error": return <ErrorView error={state.error} />; // error is Error, not Error | null | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment