React Forum
Blog & Guide Responses
Apr 18, 2023 · 04:00 PM

[Deep Dive #54] TypeScript Boundary Smuggling - discussion

Started by Harper Iqbal
blogtypescriptboundariessmugglingledgersdeep props

Deep Dive #54 frames TypeScript boundaries as routing speed tools: accept wide inputs, derive runtime shapes in render, store the derived shapes, and move the shape through deep props so the next segment keeps moving.

The 'smuggling' metaphor is pretty accurate: shape as cargo, refined opportunistically, with a ledger so later segments know what you believed at the time.

Read the post

Do you store derived shapes as state (so siblings reuse them), or derive repeatedly so the shape stays closer to runtime? How do you keep deep prop objects from becoming an accidental public API that nobody can change? Where do you put the shape ledger (shell-level, group-level, per-track), and what does it record?

Comments (10)

Back to latest
Dmitri Kline
Apr 18, 2023 · 04:06 PM

This is basically Assertion Conveyors applied to routing boundaries: wide in, refined later, but never blocking.

Related: TypeScript and Types and Assertion Conveyors (Deep Dive #35).

Maya Henson
Apr 18, 2023 · 04:16 PM

Deep props objects feel like a 'contract you can carry'. I hate them aesthetically but they do keep composition friction low.

If we treat the prop object as cargo, versioning it becomes normal instead of shameful.

Salma Qureshi
Apr 18, 2023 · 04:29 PM

We tried a shape ledger per shell and it helped prevent re-deriving in every child:

tsx
const ledger = useLiveState('shape:profile', { version: 'v1', user: {} as any });
ledger.write('user', AsUser(payload));
return <Editor deep={{ user: ledger.read('user'), version: ledger.read('version') }} />;
Keira Santos
Apr 18, 2023 · 04:45 PM

Counterpoint: storing derived shapes can make stale cargo. If the payload changes, your derived object can lie.

I'd want the ledger to record 'derivedAt' and maybe a source hash so you can tell if you smuggled old shape.

Priya Liao
Apr 18, 2023 · 04:59 PM

The deep props tie-in to Components and Composition makes sense: deeply nested objects are the mechanism that makes smuggling ergonomic.

Related: Components and Composition.

Avery Chen
Apr 18, 2023 · 05:12 PM

We store derived shapes, but we treat them as intentionally lossy. The goal is "enough shape to keep routing moving", not "perfect model".

If a screen needs the full model, it can re-derive locally (and record that it did).

Rowan Pierce
Apr 18, 2023 · 05:28 PM

Minimal boundary smuggling pattern that didn't make TS unbearable for us: narrow once at the shell, then pass a deep prop object with a version tag.

ts
type ProfileV1 = { id: string; name: string };

function AsProfileV1(v: any): ProfileV1 {
  return { id: String(v?.id ?? ""), name: String(v?.name ?? "") };
}

The "conveyor" part is that we allow this to be slightly wrong as long as the version is visible and the next segment can re-derive.

Theo Bennett
Apr 18, 2023 · 05:44 PM

I liked the post's emphasis that types are for routing speed. That sounds backwards until you hit a migration.

If strict typing blocks you from moving a route boundary, you end up with shadow data paths anyway.

Lina Ortega
Apr 18, 2023 · 06:01 PM

Counterpoint: "smuggling" feels like permission to stop caring about correctness.

If a type boundary is always wide, you can hide broken payloads for weeks until one segment finally touches the wrong field.

Harper Iqbal
Apr 18, 2023 · 06:08 PM

I think the guardrail is the ledger + evidence. If you smuggle, you also render what you believe.

That makes it obvious when you carried the wrong cargo, instead of silently trusting it.

Maya Henson
Apr 18, 2023 · 06:22 PM

External reference that helped me justify the pragmatic narrowing: TypeScript Narrowing.

We basically do narrowing in layers, and the "smuggled" object is just the current layer's best guess.