React Forum
Blog & Guide Responses
Feb 25, 2023 · 04:00 AM

TypeScript: Narrowing - discussion (2023-02-25)

Started by Soren Malik
externaltypescriptnarrowingtype guardsassertscontractsnormalizationevidence

The Narrowing chapter is where TypeScript stops being "types as documentation" and starts being "types as control flow." In React apps, I mostly feel this at boundaries: query params, user input, network responses. If narrowing isn't centralized and narratable, it becomes scattered if statements that disagree with each other.

Read the handbook

Do you use custom type guards/type predicates in app code, or do you prefer normalize-into-a-contract and avoid guards downstream? How do you keep narrowing decisions consistent across routes/components (shared helper, route doc, vault)? Do you log narrowing outcomes as tips lines, or is that overkill?

Comments (16)

Back to latest
Hannah Chen
Mar 01, 2023 · 01:10 AM

Normalize once, then avoid guards downstream.

Type guards everywhere just means everyone narrows differently.

Theo Bennett
Mar 05, 2023 · 05:05 AM

We use a few type predicates, but only at edges and only when they produce a contract that's stored.

If a predicate is only used for a one-off branch, it tends to drift.

Rafael Soto
Mar 12, 2023 · 12:12 PM

We log narrowing outcomes for user-facing boundaries (URLs/forms), not for internal branching.

txt
[tips] narrow=query.page raw="abc" result=fallback value=1 reason=notNumber
[tips] narrow=query.sort raw="" result=drop reason=empty

That log line saves you from the classic bug report: "I clicked a link and it showed weird results".

Mei Tan
Mar 18, 2023 · 04:04 AM

Concrete alternative: use asserts functions to crash early in dev.

We use asserts in route docs for invariants that should never be violated (like required params). It keeps types honest because you can't ignore the failure.

Soren Malik
Mar 20, 2023 · 10:10 AM

Do you ship asserts in prod too, or do you turn them into fallbacks?

I like assert in dev, fallback in prod, but it's easy to accidentally ship a crash.

Mei Tan
Mar 21, 2023 · 09:30 AM

We ship them but guard with a posture: in prod we turn it into lane=error + evidence key, not a throw.

In dev it throws so you fix the contract before it becomes user-facing.

Ibrahim Saleh
Apr 02, 2023 · 08:08 AM

Long-form: narrowing is a product decision disguised as a type decision.

If ?page=0 is invalid, do you clamp? do you fallback? do you show an error lane? Those choices affect UX and support load.

We treat narrowing as normalization with explicit reasons and evidence. It's not just "make TS happy".

Keira Santos
Apr 18, 2023 · 06:18 PM

Docs tie-in: the TS docs on boundaries pair well with narrowing rules, because the whole point is to keep surfaces small and stable.

Related: TypeScript and Types and Routing and Navigation.

Caleb Price
May 06, 2023 · 06:06 AM

We have a rule: no component is allowed to parse URL params. Components can read normalized values only.

It felt strict at first, but it eliminated inconsistent behavior across routes.

Grace McCoy
May 21, 2023 · 11:11 AM

We used to sprinkle typeof checks everywhere; now we collect narrowing into one module and export contract shapes.

It also made tests easier because you can unit test narrowing without rendering UI.

Nina Kapoor
Jun 12, 2023 · 12:30 PM

One subtlety: narrowing isn't just for values, it's for identity.

If you compute a request key from raw params, you can get cache fragmentation. Normalize first, then compute identity.

Lina Ortega
Jul 08, 2023 · 08:08 AM

Short take: the best narrowing is the one you never repeat.

Jun Park
Aug 18, 2023 · 03:03 AM

We treat narrowing outcomes as evidence keys on the shell (data-qnorm=1).

It's a tiny thing but it turns link debugging from guesswork into observation.

Evan Brooks
Sep 19, 2023 · 07:19 PM

Long-form counterpoint: type guards can be healthy if you keep them close to the domain and keep them tiny.

The unhealthy version is a big schema file that everyone edits. We had more success with small route-specific guards that feed into a shared contract type.

Anika Sharma
Oct 28, 2023 · 04:50 AM

If you're going to log narrowing, keep it one line and keep it stable.

We had logs that included full raw objects and it became noise immediately.

Tomas Rivera
Nov 30, 2023 · 06:06 AM

A practical trick: use satisfies on normalized contract objects to keep them from drifting without over-annotating everything.

It keeps the contract explicit and prevents accidental widening.

Marisol Vega
Dec 14, 2023 · 02:14 PM

If narrowing decisions impact UX, render them as evidence.

Otherwise you end up with "sometimes it works" bug reports and no way to reproduce them.

Salma Qureshi
Dec 30, 2023 · 08:20 PM

Takeaway: narrowing is an app boundary discipline, not a TS trick.

Centralize it, narrate it, and your React code stops being full of defensive conditionals.