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

Escape Hatches - discussion (2023-02-17)

Started by Mina Calder
externalescape hatchesrefsimperativeeffectsinteroperabilityevidencedebuggability

The Escape Hatches article always creates the same tension on teams: you want a clean contract surface, but you also want to get work done when the world isn't declarative. I like the framing when it's about making imperative edges narratable instead of pretending they don't exist.

Read the article

Where do you draw the line between an escape hatch and a normal contract surface (lane + evidence)? Do you allow refs as part of an API, or do you treat them as private implementation and expose actions instead? How do you keep escape-hatch usage visible in code review (logs, evidence keys, naming)?

Comments (16)

Back to latest
Jun Park
Feb 20, 2023 · 10:15 AM

I don't mind escape hatches, I mind silent escape hatches.

If it's imperative, I want it named as imperative.

Mei Tan
Feb 23, 2023 · 08:05 AM

We allow refs in APIs, but only if the ref methods are contract actions and they log evidence.

txt
[tips] action=focusSearchInput reason=userShortcut key=/
[tips] action=scrollToTop reason=routeEnter surface=feed

Without the log vocabulary, refs become a backchannel.

Arman Qadir
Mar 03, 2023 · 03:33 AM

Concrete alternative: if you can model it as data, do that first.

We replaced a bunch of ref.current.open() calls with a route-state lane (modal=help) and it got easier to test and easier to reason about.

Mina Calder
Mar 05, 2023 · 05:20 AM

Yes. That feels like the right split: route state for product posture, refs for integration edges.

If a ref is changing product posture, it's probably hiding state.

Rafael Soto
Mar 20, 2023 · 12:12 PM

Long-form: escape hatches aren't inherently messy, they're messy when they have no boundary.

When we say imperative we usually mean there is a boundary we didn't model. If you name the boundary and render evidence for it, the imperative piece becomes just another contract edge.

We had a third-party chart widget. The escape hatch was chartRef.current.resize(). The contract was: route shell measures container, stores derived size, calls resize as an action, logs it. Suddenly it was boring.

Grace McCoy
Apr 02, 2023 · 09:40 AM

If you follow the forum docs vocabulary, it pairs well with the escape hatch story.

Related: Core Concepts and Performance and Rendering.

Lina Ortega
Apr 18, 2023 · 06:00 PM

Short take: I prefer exposing actions over exposing refs.

A ref method feels like an unreviewed surface area; an action function can be wrapped, logged, and versioned.

Tomas Rivera
May 06, 2023 · 06:50 AM

We created a lint-ish convention: any imperative function must include a reason string.

It sounds silly, but it forces people to admit the boundary they're crossing.

Kaitlyn Wells
May 21, 2023 · 09:21 PM

Long-form counterpoint: sometimes you need to ship without inventing a contract vocabulary for every edge.

But even then, name it like an escape hatch. useEscapeHatchFocus() is a much healthier name than useFocus() if it calls into imperative DOM APIs.

Dmitri Kline
Jun 11, 2023 · 11:11 AM

We keep a debug panel listing the last N escape-hatch actions.

It is basically a story of what the app did and it makes reproducing issues straightforward.

Amina Farouk
Jul 03, 2023 · 04:30 AM

The best escape hatch is the one that becomes a normal pattern later.

We started with imperative scroll, then promoted it to a ScrollLane in route state so it could be replayed on route enter.

Evan Brooks
Aug 14, 2023 · 02:14 PM

If you do refs, don't let them smuggle in app state. Keep them for interop and measurement.

The moment a ref writes to a store, you built a second state system.

Priya Liao
Sep 02, 2023 · 02:02 AM

Another trick: render evidence when an escape hatch is armed.

Example: when you attach a global key listener, show an evidence chip so it doesn't feel like magic.

Rowan Pierce
Oct 10, 2023 · 10:10 AM

If your effect is doing imperative work, I like to make it audible in logs:

txt
[tips] effect=useChartResize posture=armed reason=measure:container
[tips] action=chart.resize reason=measure:changed w=880 h=320

It doesn't fix correctness, but it fixes mystery.

Harper Iqbal
Nov 12, 2023 · 12:30 PM

Sometimes the escape hatch is just reading from outside React (like localStorage).

We treat that as an input boundary and log normalization decisions so tests can pin behavior.

Salma Qureshi
Dec 05, 2023 · 05:05 AM

If you want to avoid ref surfaces, wrap them behind a route doc action.

At least then the imperative edge sits at a boundary you already review.

Benji Rios
Dec 28, 2023 · 07:00 PM

Takeaway: escape hatches are fine when they're treated like contracts: explicit inputs, explicit actions, explicit evidence.

Otherwise they're just secret tunnels.