Repository navigation
perf(Android, Tabs): apply tab configuration changes at the end of the React transaction - #4976
Draft
kkafar wants to merge 3 commits into
Conversation
kkafar
added this pull request to stack #4860
October 19, 2026 11:19
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
kkafar
force-pushed
the
@kkafar/rfc-1823-android-tabs-batch-item-updates
branch
from
October 19, 2026 12:49
57467de to
fbfec69
Compare
kkafar
force-pushed
the
@kkafar/rfc-1823-android-tabs-batch-item-updates
branch
from
October 19, 2026 13:06
fbfec69 to
dac50d7
Compare
kkafar
force-pushed
the
@kkafar/rfc-1823-android-tabs-batch-item-updates
branch
from
October 19, 2026 13:19
dac50d7 to
d1bb5ad
Compare
kkafar
force-pushed
the
@kkafar/rfc-1823-android-tabs-batch-item-updates
branch
from
October 19, 2026 14:20
d1bb5ad to
6fc9c98
Compare
2 of 6 tasks
kkafar
force-pushed
the
@kkafar/rfc-1823-android-tabs-batch-item-updates
branch
from
October 20, 2026 11:26
6fc9c98 to
bbc8c4a
Compare
kkafar
force-pushed
the
@kkafar/rfc-1823-android-tabs-batch-item-updates
branch
3 times, most recently
from
October 20, 2026 13:01
49e4828 to
19cc028
Compare
kkafar
force-pushed
the
@kkafar/rfc-1823-android-tabs-batch-item-updates
branch
from
October 20, 2026 19:25
19cc028 to
ea94447
Compare
kkafar
force-pushed
the
@kkafar/rfc-1823-android-tabs-batch-item-updates
branch
2 times, most recently
from
October 21, 2026 09:39
18c9a16 to
a38412a
Compare
kkafar
force-pushed
the
@kkafar/rfc-1823-android-tabs-batch-item-updates
branch
from
October 21, 2026 10:01
a38412a to
2fa7395
Compare
kkafar
force-pushed
the
@kkafar/rfc-1823-android-tabs-batch-item-updates
branch
from
October 21, 2026 10:27
2fa7395 to
78158a9
Compare
kkafar
force-pushed
the
@kkafar/rfc-1823-android-tabs-batch-item-updates
branch
from
October 21, 2026 10:36
78158a9 to
f456f40
Compare
2 of 5 tasks
kkafar
force-pushed
the
@kkafar/rfc-1823-android-tabs-batch-item-updates
branch
from
October 21, 2026 17:20
f456f40 to
2a44942
Compare
kkafar
force-pushed
the
@kkafar/rfc-1823-android-tabs-batch-item-updates
branch
from
October 21, 2026 17:43
2a44942 to
8a2bdcf
Compare
kkafar
force-pushed
the
@kkafar/rfc-1823-android-tabs-batch-item-updates
branch
3 times, most recently
from
October 21, 2026 18:38
0fa6b66 to
9d8597d
Compare
kkafar
force-pushed
the
@kkafar/rfc-1823-android-tabs-batch-item-updates
branch
from
October 21, 2026 18:46
9d8597d to
a5d4810
Compare
…e React transaction Tab configuration changes used to reach the tab bar as they arrived: a title, icon, badge or accessibility change updated that tab's menu item synchronously inside the prop setter, and appearance changes posted a whole-bar update. React delivers props in batches, so one transaction could update the same menu item several times, and the posted bar update could run apart from the transaction that caused it. Mirror `StackContainer.beginUpdateBatch` / `endUpdateBatch`: `TabsHost.willMountItems` / `didMountItems` now bracket the transaction with `TabsContainer.beginUpdateBatch` / `endUpdateBatch`. Inside a batch, tab invalidations only record their flags (per menu item, plus `isMenuItemsInvalidated` / `isNavigationMenuAppearanceInvalidated` on the container) and are applied once by the flush at the end of the batch (`updateMenuItemsIfNeeded` after the whole-bar update). Changes raised outside a batch (asynchronous icon loads, configuration changes) post a flush of their own. A detached container keeps accumulating and flushes when attached, as before. This moves menu item updates to the end of the transaction, so it is a timing change, not a structural one. Closes software-mansion/react-native-screens-labs#1957. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ateScheduler TabsContainer posted `flushPendingUpdates` for every change raised outside a React transaction (`tabBarHidden`, `scheduleFlushOutsideUpdateBatch`), so several changes in one main loop iteration sent several messages, each running its own flush. Inside a transaction it held a boolean and flushed at its end whether or not anything had changed. Add `ContainerUpdateScheduler` to core's containers/common: it takes a single update callback and coalesces requests into one run of it. `requestUpdate` posts one run to the main thread's message queue and does nothing while that run is pending; between `beginBatch` and `endBatch` requests are only recorded and `endBatch` runs the update once if any was requested; `flush` runs it right away (inside a batch too) and drops the posted run; `cancel` drops it without running. TabsContainer now requests an update after each invalidation (tab bar visibility, navigation state request, adding and removing items, tab configuration changes), `begin/endUpdateBatch` delegate to the scheduler, `flushPendingUpdates` flushes it, and `tearDown` cancels a pending run. The `tabBarHidden` change is applied at the end of the transaction that set it instead of in a posted runnable. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xx1CNDu9HyxkbE3nRiXrfb
A tap reaches `TabsContainer.onMenuItemSelected` through Material's `menu.performItemAction` (the menu view's click listener), not through `setSelectedItemId`, so `onAfterSetSelectedItemId(USER)` never runs for it. The appearance invalidated by the selection change was only applied by the next container update, usually the React transaction that echoes the new navigation state from JS. Request an update for user-originated selection changes, so the scheduler applies the new tab's appearance on the next main loop iteration. Programmatic changes keep applying it later in the same `performContainerUpdate` pass. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xx1CNDu9HyxkbE3nRiXrfb
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Kacper · project thread
Description
Follow-up to #4920, raised in its review. Tab configuration changes reached the tab bar as they arrived. A title, icon, badge or accessibility change updated that tab's menu item synchronously, inside the prop setter. Appearance changes posted a whole-bar update. React delivers props in batches, so one transaction could update the same menu item several times, and the posted bar update could run apart from the transaction that caused it.
Now the tab container batches them, like
StackContainerdoes: changes raised during a React transaction are recorded and applied once when it ends.When the container updates is decided by a new reusable core primitive,
ContainerUpdateScheduler(core/containers/common). It takes a single update callback and coalesces any number of update requests into one run: outside a batch it posts one run to the main thread's message queue (no more onepost { flushPendingUpdates() }per change), inside a batch it only records the request and runs once when the batch ends, and the update can be flushed at any moment.TabsContaineris its first user.This is a timing change, not a structural refactor: menu item and appearance updates raised inside a transaction now land at the transaction's end, in the same frame, instead of in the prop setter or a posted runnable.
Closes https://github.com/software-mansion/react-native-screens-labs/issues/1957.
Stacked on #4975.
Changes
TabsHost.willMountItems/didMountItemsnow callTabsContainer.beginUpdateBatch/endUpdateBatch(previouslydidMountItemscalledflushPendingUpdates), mirroringStackHost/StackContainer.TabsContainer.onItemInvalidatedonly records: the menu item's pending flags (as before), plus the container'sisNavigationMenuAppearanceInvalidated(size changes, or appearance changes of the selected tab, under the same attached / selected checks as before) or the newisMenuItemsInvalidated(title / icon / badge / accessibility).performContainerUpdateapplies them once at the end of the batch. The newupdateMenuItemsIfNeededruns after the whole-bar appearance update, which already consumed the flags of the items it updated, and is skipped until the first tab is selected.ContainerUpdateScheduler(commit "schedule container updates with ContainerUpdateScheduler"):requestUpdate()posts one run to the main thread'sHandler; further requests do nothing until it runs.beginBatch()/endBatch(): requests are only recorded, andendBatchruns the update once if any was requested. A run posted before the batch began waits for its end. Batches don't nest.flush()runs the update right away, inside a batch too, and drops the posted run.cancel()drops it without running.updateScheduler.requestUpdate(): thetabBarHiddensetter (replacing itspost { flushPendingUpdates() }),setPendingNavigationStateUpdate, adding and removing items, and tab configuration changes (replacingscheduleFlushOutsideUpdateBatch).begin/endUpdateBatchdelegate to the scheduler,flushPendingUpdates()isupdateScheduler.flush(), its old body is the scheduler's callbackperformContainerUpdateIfNeeded, andtearDowncancels a pending run. The batch end now runs the update only when something requested one, instead of on every React transaction in the app.submitSelectionOfTabsScreenWithKey) are applied by the scheduler's posted run, at most one per main loop iteration. AtabBarHiddenchange is applied at the end of the transaction that set it, instead of in a posted runnable.menu.performItemAction, notsetSelectedItemId, soonAfterSetSelectedItemId(USER)never ran for it and the new tab's appearance waited for the next container update (usually the React transaction echoing the navigation state from JS).onMenuItemSelectednow requests an update for user-originated selection changes.onAttachedToWindowflushes, as before.Test plan
Run after every rebase of this branch, last on f4283d0:
yarn lint-android(layer check +spotlessCheck) passes.:react-native-screens:compileDebugKotlinsucceeds.On device (Android, FabricExample, release build, Pixel 3a API 34 emulator), on the batching commit before the scheduler commits (8a2bdcf, before the rebase):
FabricExample/e2e/single-feature-tests/tabs/plusexample-tests/bottomTabs: 11 suites, 66 tests pass (test-tabs-item-badgeandtest-tabs-navigation-state-search-tabare iOS-gated). Crash buffer empty.single-feature-tests/tabs/test-tabs-item-icon-tint-and-size: runtime icon size (32 / 56 / default), active indicator (96x64 / auto), tinting modes and the badge toggle apply immediately and revert; other tabs stay unchanged.test-tabs-item-icon: asynchronously loaded image icons appear (they arrive outside a React transaction); the selected icon swaps.test-tabs-item-badge: badge colours follow the selected tab on every switch (whole-bar update re-applies all badges).test-tabs-general-appearance-androidandissue-tests/TestBottomTabs: tab items expose their testIDs (viewIdResourceName) and content descriptions, unchanged across tab switches and label modes.test-tabs-item-title: titles, colours and per-tab fonts follow selection.To re-run with the scheduler commits:
test-tabs-item-icon(async icons go through the posted run) andtest-tabs-item-icon-tint-and-size.tabBarHidden) applies and the content insets follow.Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_01Xx1CNDu9HyxkbE3nRiXrfb