React Forum
Blog & Guide Responses
Feb 20, 2023 · 04:00 PM

TypeScript: Everyday Types - discussion (2023-02-20)

Started by Rowan Pierce
externaltypescripteveryday typescontractslanesevidencenarrowingroute docs

The Everyday Types chapter is the kind of thing you skim until you're in the middle of a refactor and realize half the churn is self-inflicted: wide, mushy shapes at the edges and no shared vocabulary for what counts as a stable surface. I keep coming back to the forum's contract + evidence idea here: types can describe the contract, but only if you keep the contract small enough to actually narrate.

Read the handbook

When you're typing UI posture/lanes, do you prefer string unions, discriminated objects, or something more structural? How do you keep boundary types wide while keeping route docs and hook returns narrow and stable? Do you treat evidence tokens as typed outputs, or do you let them be freeform strings?

Comments (14)

Back to latest
Hana Saito
Feb 24, 2023 · 09:12 AM

String unions for lanes, discriminated objects for intent.

If someone can't explain it out loud, the type is too fancy.

Dmitri Kline
Mar 01, 2023 · 04:40 AM

We started typing evidence tokens explicitly and it made reviews dramatically calmer. When evidence was just string, people would invent a new phrase every time and tooling couldn't help.

ts
type Lane = 'idle' | 'pending' | 'ok' | 'error';
type Evidence =
  | `lane:${Lane}`
  | `net:http:${number}`
  | `query:canon:${0 | 1}`
  | `scope:locale:${string}`;

type Contract<T> = { lane: Lane; value?: T; evidence: Evidence[] };

The point isn't perfection, it's making the vocabulary finite so people stop smuggling meaning into prose.

Marisol Vega
Mar 06, 2023 · 04:16 PM

Counterpoint: I'd rather keep evidence tokens untyped and validate at runtime.

Types are static; evidence is observed. We log evidence and ship it through pipelines that aren't TS-aware anyway.

Rowan Pierce
Mar 08, 2023 · 10:10 AM

I think both can work. The win for us was typing the high-value evidence (lanes, sources, canonicalization) and leaving the rest as freeform notes.

Otherwise it becomes a taxonomy project.

Ibrahim Saleh
Mar 18, 2023 · 07:30 AM

Long-form: the hardest part is not picking union vs object, it's preventing boundary drift. Teams add fields to the contract because it's convenient in one component, and then 6 weeks later the contract is a dumping ground.

Our rule: boundary types can be wide, but contracts must have an owner and must narrate themselves with a small [tips] story line.

txt
[tips] contract=SearchDoc lane=pending reason=query:commit evidence=lane:pending
[tips] contract=SearchDoc lane=ok reason=fetch:resolve evidence=lane:ok

If you can't write those lines, you probably shouldn't be adding that field.

Sofia Martinez
Apr 02, 2023 · 05:05 AM

We keep boundary types wide using unknown + a single parse/normalize function.

Then the internal contract surface is narrow and boring.

Jun Park
Apr 19, 2023 · 12:30 PM

Concrete alternative: don't model intent as a big discriminated union.

We model intent as small, local unions per route doc. Global intent types always turned into a God type.

Keira Santos
May 07, 2023 · 09:09 AM

Docs tie-in: the through-line is "narrow surfaces, wide inputs" and keeping type boundaries honest.

Related: TypeScript and Types and Core Concepts.

Caleb Price
May 28, 2023 · 06:18 PM

We made our types more useful by making them reflect runtime normalization.

Example: instead of page?: number, we store page: number and guarantee it is normalized at the boundary. That single decision eliminated a ton of optional chaining nonsense.

Nina Kapoor
Jun 18, 2023 · 03:03 AM

I like discriminated objects for lanes because you can carry reason/source without adding parallel fields:

ts
type Lane<T> =
  | { lane: 'pending'; reason: string }
  | { lane: 'ok'; value: T; evidence: string }
  | { lane: 'error'; error: { code: string; message: string }; evidence: string };

It's slightly more verbose, but it prevents the classic bug where you have lane='error' and value still set.

Lina Ortega
Jul 12, 2023 · 12:12 PM

Short take: the best types are the ones that force you to delete fields instead of adding them.

Evan Brooks
Aug 22, 2023 · 08:22 AM

We tried typing evidence tokens and it was nice, but we overdid it.

The compromise: type the top-level prefixes, keep suffixes freeform. That keeps the vocabulary small without turning it into a schema registry.

Tomas Rivera
Sep 14, 2023 · 02:14 PM

Long-form counterpoint: types can also mask bad architecture because they make it feel safe.

I care less about whether the type is perfect and more about whether the contract can be observed. If the UI doesn't render evidence and the logs don't narrate transitions, a beautiful type is just a fancy wrapper around mystery.

Anika Sharma
Oct 28, 2023 · 04:04 AM

We made boundary parsing testable by pinning the normalization rules in a single file and writing tiny unit tests for it.

Then everything downstream can assume the narrow type is real.

Salma Qureshi
Dec 12, 2023 · 06:06 AM

Takeaway: pick the simplest type shape that still encodes invariants, then invest the rest of your energy in evidence and narration.

Everyday Types is the foundation, but the contract discipline is what keeps it from collapsing.