Repository navigation
[Tracking] [Performance] Standardize the "defer-after-paint" / "dismiss-first, compute-later" pattern across the App #88596
Description
Context
@JakubKorytko recently landed a single, working implementation of a "dismiss-first / compute-later" + "defer non-critical work until after the destination is visible" pattern across three PRs:
- #87404 — Pre-render Search under RHP for instant post-submit navigation on mobile
- #87963 — Refactor: Extract
SubmitExpenseOrchestrator+ centralize telemetry - #87768 — Extend dismiss-first navigation fast paths + deferred writes
This is currently the only implementation of this idea in the codebase. The measured gains are large (e.g. -86% on dismiss_modal_and_open_report mobile, -73% on dismiss_modal_only wide-RHP — see #87768), so other engineers now want to apply the same approach to other hot paths:
- Tomasz Misiukiewicz is drafting a reusable
DeferredMountcomponent (usesstartTransition) for dropdowns/popovers/offscreen UIs (e.g. the "More" menu in the money request report header), where heavyuseOnyxsubscriptions currently run during navigation even though the dropdown is closed. - @adhorodyski is exploring a
scheduleAfterTransition(fn)style helper to make call-time deferral more declarative. - Yehor's existing
TransitionTrackeris the primitive used by Jakub for "defer until navigation/RHP animation is finished" so the JS thread isn't overloaded mid-animation.
We want to lock in Jakub's pattern as the standard now, before each new use case re-invents a slightly different mechanism (startTransition vs useFocusEffect flushes vs deferred-write channels vs requestAnimationFrame vs InteractionManager).
Why this matters
- The pattern is delivering large, measurable wins, and we want it to be the default mental model — not a one-off owned by a single engineer.
- Without a documented standard, the next 3–5 features that want the same UX will fork the implementation, and we'll end up reconciling them later (more expensive than agreeing once now).
- Concurrent / deferred patterns improve perceived performance but can make some Sentry spans look worse if they wait for full mount. We need telemetry guidance aligned with the new model (raised by Mariusz).
- Inline defer logic is already starting to "pollute" small screen components (
ReportScreenpicked up inlineuseState+useFocusEffectfor a singledismiss_modal_and_open_reportcase — see Adam's review on #87768). A shared API would keep this from growing per-feature. - External contributors need a written playbook; today the only way to learn the pattern is to read three large merged PRs.
Goal
Take the single existing implementation (Jakub's three PRs above) as the baseline, agree on the canonical API surface, document it, and update tooling so the next engineers wanting the same UX use it instead of forking.
Concretely:
-
Extract the canonical primitives from the existing PRs. From Jakub's implementation, the building blocks are roughly:
deferredLayoutWrite.ts— channel-based "defer this write until the destination screen flushes" helper.SubmitExpenseOrchestrator— render-prop that picks fast vs. slow path per navigation topology.TransitionTracker(Yehor) — "navigation / animation finished" signal.- Synchronous
Navigation.dismissModal— dismiss-first primitive. useFocusEffect-based flush on the destination screen.
Decide which of these become the public, reusable API and which stay private to the submit flow.
-
Decide the API surface for the upcoming use cases, e.g.:
<DeferredMount>for subtrees not needed for first paint (dropdowns, popovers, offscreen UI, heavyuseOnyxconsumers) — Tomasz's proposal.- A
scheduleAfterTransition(fn)helper for imperative call-sites — Adam's exploration.
Both should be built on top of the same primitives Jakub already shipped, not parallel implementations.
-
Decide what NOT to do. E.g. compute-on-open shifts cost to interaction time and was rejected for the dropdown case (Tomasz already explored this). Document the rejected alternatives so we don't re-litigate.
-
Define telemetry guidance so spans measure the right thing (perceived ready vs. fully mounted vs. INP), aligned with Mariusz's concern. Make sure existing spans like
ManualSubmitToDestinationVisiblestay meaningful. -
Write it down in:
contributingGuides/STYLE.md(or a newcontributingGuides/PERFORMANCE.md)- The PR reviewer checklist / code-review skill (Adam suggested making the AI reviewer flag inline defer logic in screen components)
- The
react-native-best-practicesskill
-
Announce in
#expert-contributorsonce agreed, with examples pulled directly from Jakub's PRs and clear "when to use / when not to use" guidance.
Proposed drivers
- @JakubKorytko (already merged the baseline implementations in #87404, #87963, #87768)
- @mountiny (process / docs / standardization)
Inputs we want
- Tomasz Misiukiewicz —
DeferredMountproposal + comparison vs. the "Switches" / orchestrator pattern Jakub already merged - @adhorodyski —
scheduleAfterTransitionexploration + concerns about screen component pollution (raised on #87768) - Yehor —
TransitionTrackerprimitive - Mariusz — telemetry / perceived-perf measurement implications
Out of scope
- Implementing the standard in every feature today. This issue is about agreeing on the API on top of Jakub's existing primitives and writing it down.
- New variants of the pattern. If a use case truly can't fit the standard, that's a reason to extend the standard, not fork it.
Definition of done
- Pattern + API documented in
contributingGuides/ - Reviewer checklist / code-review skill updated to flag ad-hoc inline deferral
- Announcement posted in
#expert-contributors - At least one follow-up issue opened to adopt the standard in an upcoming use case (e.g. Tomasz's
DeferredMountfor the report header "More" menu) - Migrate
useDeferNonEssentials(ReportScreen) off its inline deferral ontoNavigationDeferredMount(or extend it, since the hook only defers after a submit)
Activity
roryabraham commentedon Apr 23, 2026
I want to help review the PR, if that's ok
mountiny commentedon Apr 23, 2026
Would love that, thanks!
melvin-bot commentedon Apr 28, 2026
melvin-bot commentedon Apr 30, 2026
JakubKorytko commentedon May 4, 2026
Not OD, I was OOO, will focus on it this week.
melvin-bot commentedon May 4, 2026
JakubKorytko commentedon May 5, 2026
roryabraham commentedon May 5, 2026
Removing @TMisiukiewicz as issue owner since @JakubKorytko is working on this
85 remaining items
[-][Due for payment 2026-10-16] [Tracking] [Performance] Standardize the "defer-after-paint" / "dismiss-first, compute-later" pattern across the App[/-][+][Tracking] [Performance] Standardize the "defer-after-paint" / "dismiss-first, compute-later" pattern across the App[/+]melvin-bot commentedon Oct 14, 2026
@mkhutornyi
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.8-5 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
JakubKorytko commentedon Oct 15, 2026
Update:
melvin-bot commentedon Oct 15, 2026
@mkhutornyi
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.9-4 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
JakubKorytko commentedon Oct 20, 2026
@MelvinBot per #102425 (comment) may you add this point to the Definition of done of this issue?
useDeferNonEssentials(ReportScreen) off its inline deferral ontoNavigationDeferredMount(or extend it, since the hook only defers after a submit)MelvinBot commentedon Oct 20, 2026
Done. I added this item to the end of the Definition of done checklist:
useDeferNonEssentials(ReportScreen) off its inline deferral ontoNavigationDeferredMount(or extend it, since the hook only defers after a submit)The rest of the issue body is unchanged.
view run
melvin-bot commentedon Oct 20, 2026
@mkhutornyi Eep! 4 days overdue now. Issues have feelings too...
JakubKorytko commentedon Oct 21, 2026
not OD, update:
useDeferNonEssentialsmigration ontoNavigationDeferredMount.