Skip to content

[Tracking] [Performance] Standardize the "defer-after-paint" / "dismiss-first, compute-later" pattern across the App #88596

Open
2 of 2 issues completed

Description

@mountiny

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:

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 DeferredMount component (uses startTransition) for dropdowns/popovers/offscreen UIs (e.g. the "More" menu in the money request report header), where heavy useOnyx subscriptions 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 TransitionTracker is 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 (ReportScreen picked up inline useState + useFocusEffect for a single dismiss_modal_and_open_report case — 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:

  1. 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.

  2. Decide the API surface for the upcoming use cases, e.g.:

    • <DeferredMount> for subtrees not needed for first paint (dropdowns, popovers, offscreen UI, heavy useOnyx consumers) — 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.

  3. 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.

  4. 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 ManualSubmitToDestinationVisible stay meaningful.

  5. Write it down in:

    • contributingGuides/STYLE.md (or a new contributingGuides/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-practices skill
  6. Announce in #expert-contributors once agreed, with examples pulled directly from Jakub's PRs and clear "when to use / when not to use" guidance.

Proposed drivers

Inputs we want

  • Tomasz Misiukiewicz — DeferredMount proposal + comparison vs. the "Switches" / orchestrator pattern Jakub already merged
  • @adhorodyski — scheduleAfterTransition exploration + concerns about screen component pollution (raised on #87768)
  • Yehor — TransitionTracker primitive
  • 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 DeferredMount for the report header "More" menu)
  • Migrate useDeferNonEssentials (ReportScreen) off its inline deferral onto NavigationDeferredMount (or extend it, since the hook only defers after a submit)
Issue OwnerCurrent Issue Owner: @mkhutornyi
Issue OwnerCurrent Issue Owner: @JakubKorytko

Activity

  1. self-assigned this
    on Apr 23, 2026
  2. roryabraham commented on Apr 23, 2026

    @roryabraham
    Contributor

    I want to help review the PR, if that's ok

  3. mountiny commented on Apr 23, 2026

    @mountiny
    ContributorAuthor

    Would love that, thanks!

  4. melvin-bot commented on Apr 28, 2026

    @melvin-bot
  5. melvin-bot commented on Apr 30, 2026

    @melvin-bot
  6. JakubKorytko commented on May 4, 2026

    @JakubKorytko
    Member

    Not OD, I was OOO, will focus on it this week.

  7. melvin-bot commented on May 4, 2026

    @melvin-bot
  8. JakubKorytko commented on May 5, 2026

    @JakubKorytko
  9. roryabraham commented on May 5, 2026

    @roryabraham
    Contributor

    Removing @TMisiukiewicz as issue owner since @JakubKorytko is working on this

  10. 85 remaining items

  11. changed the title [-][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[/+] on Oct 14, 2026
  12. melvin-bot commented on Oct 14, 2026

    @melvin-bot

    @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:

  13. JakubKorytko commented on Oct 15, 2026

    @JakubKorytko
    Member

    Update:

    • #102324 was split into a 5-part stack for easier review. Parts 1 and 2 are on production, part 3 is merged, and part 4 is in review. Part 5 turns wide pre-mount on and comes next.
    • #102425 is out of draft and ready for review. It no longer waits for the stack, because the doc does not depend on the wide-layout changes. Once it merges, it covers DoD items 1 and 2.
    • DoD item 4 is done via #94673, so after #102425 the only item left is the #expert-contributors announcement (DoD item 3).
  14. melvin-bot commented on Oct 15, 2026

    @melvin-bot

    @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:

  15. JakubKorytko commented on Oct 20, 2026

    @JakubKorytko
    Member

    @MelvinBot per #102425 (comment) may you add this point to the Definition of done of this issue?

    • Migrate useDeferNonEssentials (ReportScreen) off its inline deferral onto NavigationDeferredMount (or extend it, since the hook only defers after a submit)
  16. MelvinBot commented on Oct 20, 2026

    @MelvinBot
    Contributor

    Done. I added this item to the end of the Definition of done checklist:

    • Migrate useDeferNonEssentials (ReportScreen) off its inline deferral onto NavigationDeferredMount (or extend it, since the hook only defers after a submit)

    The rest of the issue body is unchanged.


    view run

  17. melvin-bot commented on Oct 20, 2026

    @melvin-bot

    @mkhutornyi Eep! 4 days overdue now. Issues have feelings too...

  18. JakubKorytko commented on Oct 21, 2026

    @JakubKorytko
    Member

    not OD, update:

    • #102425 is merged, which covers DoD items 1 and 2.
    • Wide pre-mount stack: parts 1-3 are on production. Part 4 addressed the latest review and is waiting for another pass. Part 5 turns wide pre-mount on and goes up right after 4 merges.
    • Left: the #expert-contributors announcement (DoD item 3) and the newly added useDeferNonEssentials migration onto NavigationDeferredMount.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

DailyKSv2OverdueSkip PaymentSkips all the payment process steps for an issue. Use it for fixed-price projects.Task

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions