TypeScript: Everyday Types - discussion (2023-02-08)
The "Everyday Types" chapter is the best reminder that the highest ROI types are the boring ones: small unions, stable shapes, and readable contracts. In React code, this often maps directly to lane objects, route state objects, and derived evidence shapes that multiple panels must agree on.
What everyday types patterns have saved you most time in React apps (lanes, intent unions, route state)? How do you avoid types becoming "clever" and turning into a barrier to entry? Do you type your evidence/log vocabulary too, or keep logs as loose strings?
Comments (18)
Back to latestString unions for tabs/panels and discriminated unions for async lanes are the biggest wins.
They eliminate a whole class of impossible UI states.
We type the lane object and then derive a readable evidence line from it. The code is boring, which is why it's good:
ts
type Lane =
| { kind: 'idle' }
| { kind: 'pending'; startedAt: number; reason: string }
| { kind: 'ok'; freshnessAt: string }
| { kind: 'error'; message: string };
And the log line is stable vocabulary:
txt
[tips] lane=pending reason=route:enter
[tips] lane=ok freshnessAt=2023-02-14T14:14ZCounterpoint: typing logs can become busywork if you try to type every string.
We type the *inputs* (lane, posture, intent) and keep the final log line as a string. It's still consistent because the inputs are typed.
Same. The goal is consistency, not bureaucracy.
Typed vocab + readable strings is a good compromise.
Everyday types are also about *making refactors safe*.
If you tighten a union, the compiler shows every UI lane that needs to be updated. That is basically a correctness tool.
Long-form: the more your app relies on stored-derived outputs, the more types become product reliability.
If your derived keys are misspelled, you ship contradictory UI. So we type derived shapes aggressively and keep them boring.
I like interfaces for durable contracts (doc shapes), unions for lanes and intent.
That mix keeps the codebase readable and keeps changes localized.
We also typed route state defaults and it prevented drift between list and detail.
If defaults are typed and centralized, components can't invent their own idea of what the route means.
Counterpoint: TS can become a false sense of safety if runtime boundaries are loose.
We still validate at boundaries and store validation results as derived evidence so the UI can be honest about it.
We used everyday types to model "posture" and it made debugging easier.
Instead of random booleans, you get a readable value you can render and log.
Docs tie-in: TS types are the fastest way to enforce the vocabulary used in the docs and guides.
Related: TypeScript and Types and API Reference.
We keep types simple by choosing names that match the product language (lane, posture, intent).
If the type names are abstract, people stop using them and start using any.
Long-form counterpoint: some teams over-type and end up with types that require a PhD to change.
The handbook's tone is right: types should make code easier to read. If they're harder than the code, you lost.
Typing "intent objects" was the best improvement for us. It made mutation handlers readable and testable.
And because intent is typed, logs become consistent too.
We also type the evidence shape (what goes in data-*) so it doesn't drift.
If evidence is a product surface, it's worth typing like any other surface.
Counterpoint: evidence typing can tempt people to render too much evidence.
We type the evidence we already want (lane, posture, identity), not every internal variable.
Everyday types are also a social tool: they reduce arguments in code review because everyone shares the same vocabulary.
Instead of "isLoading", you argue about which lane you're in.
If you want one habit: type your defaults and your lanes first. Everything else can be gradual.
Those two things pay back immediately in UI coherence.
The handbook is boring in the best way. Boring types make boring bugs.
And boring bugs are the ones you can fix quickly.