Skip to content

feat(android): add unstable_Immediate UpdateMode for synchronous state updates - #56311

Closed
intergalacticspacehighway wants to merge 1 commit into
react:mainfrom
intergalacticspacehighway:android/sync-update-mode
Closed

intergalacticspacehighway wants to merge 1 commit into
react:mainfrom
intergalacticspacehighway:android/sync-update-mode

Conversation

@intergalacticspacehighway

@intergalacticspacehighway intergalacticspacehighway commented Apr 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary:

Currently we support unstable_Immediate mode on updateState for iOS. This allows us to dispatch synchronous shadow node size updates in Expo UI SwiftUI (communicating SwiftUI children size to Yoga. Host for more details).

Since it is not enabled on android, it leads to layout shift when mixing Compose and React Native components as Compose tree currently dispatches size information asynchronously to the shadow node.

Here, the video demonstrates the asynchronous vs synchronous updateState usage (before and after applying the changes in this PR). Repro link

Screen.Recording.2026-04-02.at.8.52.27.PM.mov

More:

After enabling the update_immediate, I noticed the flicker was still happening. The issue is that update layout instruction triggers a follow up instruction when we set the shadow node size from Expo UI which gets skipped to the next frame. This is handled on iOS using _followUpTransactionRequired flag here. This seems to be added on iOS for similar reasons here when dispatching an instruction can lead to a follow-up instruction when synchronous update is triggered. So we also handle the sync follow up transaction in this PR using similar approach to iOS's.

Changelog:

[ANDROID] [ADDED] - Add unstable_Immediate UpdateMode support in updateState.

Test Plan:

Tested the changes in the repro. Also tested RN Tester, it should be a non breaking change.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 2, 2026
@github-actions

github-actions Bot commented Apr 2, 2026

Copy link
Copy Markdown

Warning

JavaScript API change detected

This PR commits an update to ReactNativeApi.d.ts, indicating a change to React Native's public JavaScript API.

  • Please include a clear changelog message.
  • This change will be subject to additional review.

This change was flagged as: POTENTIALLY_BREAKING

@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Apr 2, 2026
intergalacticspacehighway added a commit to expo/expo that referenced this pull request May 14, 2026
# Why

Below code snippet can cause layout shift on android on first mount.

```jsx
import { Box } from '@expo/ui/jetpack-compose';
import { size } from '@expo/ui/jetpack-compose/modifiers';
import { Host } from '@expo/ui/swift-ui';
import { background } from '@expo/ui/swift-ui/modifiers';
import { View } from 'react-native';

export default function ImageSymbolEffectExample() {
  return (
    <View style={{ flex: 1 }}>
      <Host matchContents>
        <Box modifiers={[size(100, 100), background('blue')]}></Box>
      </Host>
      <View style={{ width: 100, height: 100, backgroundColor: 'red' }}></View>
    </View>
  );
}
```

### Before and after the fix (left is before)

https://github.com/user-attachments/assets/b1b93781-0517-414d-90d0-7df14332652e



iOS had the same issue, it was fixed here -
#40017

Issue is currently `Host` with `matchContents` uses updateState API to
update shadow node size, this can cause layout shift as the current
dispatch is async on android. On iOS, we're using experimental sync flag
provided by RN. On Android, the flag is not exposed. We made a PR to
enable it for android -
react/react-native#56311. Until it gets
merged or any alternate API is provided by RN, we need a workaround that
can enable sync state updates on Android.

<!--
Please describe the motivation for this PR, and link to relevant GitHub
issues, forums posts, or feature requests.
-->

# How

- Android has a solution to dispatch sync event from Kotlin (or cpp) to
JS (via event emitter) (this is used by VirtualView in RN to send sync
event from native to JS). So the approach in this PR relies on the
behavior that both state update queue and events are flushed in the same
tick when a sync event is dispatched. This allows us to dispatch a state
update (which is async) but then immediately trigger a sync event which
will flush our state update synchronously 😅.
- The API had been in the core for quite some time (2024 -
react/react-native#44489)

<!--
How did you build this feature or fix this bug and why?
-->

# Test Plan

- Tested the above repro with changes in this PR, layout shift gets
fixed.
- Tested all the examples in NCL and confirmed there are no regressions.

<!--
Please describe how you tested this change and how a reviewer could
reproduce your test, especially if this PR does not include automated
tests! If possible, please also provide terminal output and/or
screenshots demonstrating your test/reproduction.
-->

# Checklist

<!--
Please check the appropriate items below if they apply to your diff.
-->

- [ ] I added a `changelog.md` entry and rebuilt the package sources
according to [this short
guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting)
- [ ] This diff will work correctly for `npx expo prebuild` & EAS Build
(eg: updated a module plugin).
- [ ] Conforms with the [Documentation Writing Style
Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)
@intergalacticspacehighway

Copy link
Copy Markdown
Contributor Author

cc - @sammy-SC can you take a look pls 🙏

@kkafar kkafar left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to chime in here, expressing that this patch is something we desire in react-native-screens for a long time now.

I've outlined why such functionality is needed in #51815.

@meta-codesync

meta-codesync Bot commented Jun 9, 2026

Copy link
Copy Markdown

@sammy-SC has imported this pull request. If you are a Meta employee, you can view this in D108023634.

intergalacticspacehighway added a commit to expo/expo that referenced this pull request Jun 10, 2026
…in RN Screens (#46604)

# Why

More robust approach than #45775. (Also
tested with the original repro)

`matchContents` `Host` view in compose shift by a frame when they mount
inside react-native-screens native stack, because their size state
update is applied asynchronously and lands after the first paint.


<!--
Please describe the motivation for this PR, and link to relevant GitHub
issues, forums posts, or feature requests.
-->

# How
- `expo-modules-core`: Added a synchronous shadow-node state update path
on Android, mirroring iOS, until we wait for this one to land
react/react-native#56311
- `@expo/ui`: Removed the previous event-emitter workaround with
`ShadowNodeSyncFlush.kt` since it is not working when we're in react
native screens. This hack relied on event emitter on view being present,
it is not present when RN screens does the layout
[here](https://github.com/software-mansion/react-native-screens/blob/a6bfad893565efc1961dbc44307daad85a797615/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt#L42)
<!--
How did you build this feature or fix this bug and why?
-->

# Test Plan

Tested in the original repro shared by @sidorchukandrew
[here](https://github.com/sidorchukandrew/expo-ui-repros).

### Before 
https://x.com/and_sidorchuk/status/2062212508862091757

### After 

https://github.com/user-attachments/assets/6a8a2ee0-690a-4a1d-be81-8f3011c84bed


<!--
Please describe how you tested this change and how a reviewer could
reproduce your test, especially if this PR does not include automated
tests! If possible, please also provide terminal output and/or
screenshots demonstrating your test/reproduction.
-->

# Checklist

<!--
Please check the appropriate items below if they apply to your diff.
-->

- [ ] I added a `changelog.md` entry and rebuilt the package sources
according to [this short
guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting)
- [ ] This diff will work correctly for `npx expo prebuild` & EAS Build
(eg: updated a module plugin).
- [ ] Conforms with the [Documentation Writing Style
Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)
@intergalacticspacehighway intergalacticspacehighway closed this by deleting the head repository Jun 11, 2026
@intergalacticspacehighway

Copy link
Copy Markdown
Contributor Author

cc - @sammy-SC @cipolleschi i created another PR here which includes tests. I just realised after the repo change i was not able to push changes to my fork while it was working for new forks (maybe i should've just waited more).

intergalacticspacehighway added a commit to expo/expo that referenced this pull request Oct 1, 2026
…performMeasureAndLayout called during measure layou (#49933)

> [!WARNING]
> **Agent-authored and NOT human-reviewed.** An automated `/verify
--fix` run for #47625 wrote this change and checked it in a sandbox; the
reasoning and evidence are in the outcome comment on that issue. Review
it as you would any external contribution.

Requested by @intergalacticspacehighway · [investigation
run](https://github.com/expo/expo/actions/runs/34403733608) · refs
#47625

Fixes #47625

`HostView` emits the `onLayoutContent` event synchronously from
Compose's `onSizeChanged` callback, which runs inside the measure pass.
A Fabric event listener that reacts synchronously —
react-native-reanimated's `onEventDispatch` hook — flushes mount items
on the same call stack and re-enters `onMeasure` of a view that is
mid-measure. Compose throws `performMeasureAndLayout called during
measure layout` and the ReactHost goes down (#47625).

This change posts the dispatch to the main looper, so the event fires
after the measure pass. `setStyleSize` timing does not change
(`ShadowNodeProxy.scheduleFlush` already defers it), so `matchContents`
sizing behaves as before.

Proof: the unpatched code crashed with the exact reported stack in 4 of
4 emulator stress runs (each within ~2–5 minutes), and a build with this
fix ran the same procedure for 784 cycles (~20 minutes) with no crash,
with `onLayoutContent` still firing.

<details><summary>Cause</summary>

Full chain, all in this repository at dcd4c59:

1. `ExpoView.requestLayout` posts `measureAndLayout`
([ExpoView.kt#L49-L55](https://github.com/expo/expo/blob/dcd4c59b5a78da5cbc43783dee5601e071c764af/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/ExpoView.kt#L49-L55)).
2. The measure pass reaches the Compose layout in
`MaybeMatchContentsLayout`, whose `Modifier.onSizeChanged` fires during
measure
([HostView.kt#L158-L162](https://github.com/expo/expo/blob/dcd4c59b5a78da5cbc43783dee5601e071c764af/packages/expo-ui/android/src/main/java/expo/modules/ui/HostView.kt#L158-L162))
and calls `dispatchOnLayoutContent`, which emits the event synchronously
([HostView.kt#L233](https://github.com/expo/expo/blob/dcd4c59b5a78da5cbc43783dee5601e071c764af/packages/expo-ui/android/src/main/java/expo/modules/ui/HostView.kt#L233)).
3. The emit goes through `KEventEmitterWrapper.emit(view, ...)` to
`FabricEventDispatcher.dispatchEvent`
([KModuleEventEmitterWrapper.kt#L103-L110](https://github.com/expo/expo/blob/dcd4c59b5a78da5cbc43783dee5601e071c764af/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/events/KModuleEventEmitterWrapper.kt#L103-L110))
— synchronous on the UI thread.
4. Reanimated's `NodesManager.onEventDispatch` runs
`performOperationsRespectingDrawPass` on the same stack. That guard only
skips the **draw** pass, not the measure pass, so it calls
`performOperations` → `FabricUIManager.scheduleMountItem`, which
executes queued mount items immediately on the UI thread.
`SurfaceMountingManager.updateLayout` then measures a `HostView` that is
still inside its own measure pass, and Compose throws.

`shadowNodeProxy.setStyleSize` / `setViewSize` are not part of the
hazard: `ShadowNodeProxy.scheduleFlush` already defers them to a
pre-draw listener plus a posted fallback
([ShadowNodeProxy.kt#L45-L73](https://github.com/expo/expo/blob/dcd4c59b5a78da5cbc43783dee5601e071c764af/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/ShadowNodeProxy.kt#L45-L73)),
and that deferral is present in every released 57.x (verified in the
57.0.2 npm tarball). The event emit at `HostView.kt#L233` is the one
remaining synchronous Fabric interaction inside the measure pass. The
synchronous Android dispatch shipped with
[#45776](#45776) (expo-ui 56.0.8) and
is carried by every released 57.x including 57.0.17.

</details>

<details><summary>Verification</summary>

Stress recipe on a hosted Android emulator (new architecture): two
screens cycled with `router.replace`; the hosts screen mounts 12 `<Host
matchContents>` with `Text` content changing every 35 ms plus a `<Host
matchContents={{vertical:true}}>` with `TextField`; 16 reanimated views
animate width/height continuously with `ReduceMotion.Never` (the
emulator's reduce-motion setting otherwise disables reanimated and
suppresses the repro); a busy-JS interval bunches React commits. App:
expo 57.0.13, react-native 0.86.2, reanimated 4.5.1, expo-router
57.0.13.

| Arm | Binary | @expo/ui native code | Result |
| --- | --- | --- | --- |
| Before (4 runs) | Expo Go, SDK 57 | bundled release, synchronous
dispatch | 4 of 4 runs crashed within ~2–5 min, exact reported stack |
| After (1 long run) | dev client, EAS build 3ff744d7 | 57.0.17 + this
fix, compiled from source | no crash through 784 cycles (~20 min);
`onLayoutContent` readout still updates |

One caveat, stated plainly: the before-arm binary is Expo Go and the
after-arm binary is a dev client, because the four EAS builds of an
unpatched dev client failed in Gradle before producing an artifact
(undiagnosed; this run's credential cannot read EAS build logs). The
crash mechanism lives in @expo/ui + React Native + reanimated code that
both binaries contain, and the same JS bundle and procedure drove both
arms. The crash-stack screenshots (attached to the linked findings
comment) show the full reported chain frame by frame, from
`dispatchOnLayoutContent-viCIZxY` through `onEventDispatch` and
`updateLayout` to the exception.

</details>

<details><summary>Checks run</summary>

In a sandbox checkout of this repository at dcd4c59 with the fix
applied, in `packages/expo-ui`: `pnpm run typecheck` (clean), `pnpm run
lint` (0 warnings, 0 errors), `pnpm test` (58 suites, 357 tests, 17
snapshots — all pass). Kotlin compile: `./gradlew
:expo-ui:compileDebugKotlin` with
`expo.autolinking.android.buildFromSource: ["expo-ui"]` succeeded on a
GitHub Actions ubuntu VM, with `expo-ui` listed without the `[📦]`
prebuilt marker (really compiled from source), and the patched file
compiled in the incremental rebuild. The EAS build 3ff744d7 that ran
on-device compiled the patched source the same way.

</details>

<details><summary>Not covered</summary>

- The `onSizeChanged` modifier in
[ModifierRegistry.kt#L695-L708](https://github.com/expo/expo/blob/dcd4c59b5a78da5cbc43783dee5601e071c764af/packages/expo-ui/android/src/main/java/expo/modules/ui/ModifierRegistry.kt#L695-L708)
dispatches the same way during measure. This change does not touch it; a
comment on #47625 reports a rarer residual crash after patching only
`HostView`, which could come from another synchronous dispatch site, but
no stack trace exists for it yet.
- iOS is untouched; the SwiftUI host does not share this code path.
- The deeper re-entrancy guard belongs to React Native
([react/react-native#56311](react/react-native#56311),
referenced from `ShadowNodeProxy`); this change removes expo-ui's own
trigger and does not wait for it.

</details>

<!-- expo-bot:fix-options v1 -->
<details><summary>Options considered</summary>

1. **Post the `onLayoutContent` dispatch (`post { ... }`) so it runs
after the measure pass.** Chosen: one line in `HostView.kt`;
`setStyleSize` timing is untouched, so `matchContents` sizing is
unchanged; the JS callback arrives one main-loop turn later, which its
documented contract ("triggered when the content completes its layout")
already allows. Matches the deferral pattern
`ShadowNodeProxy.scheduleFlush` already uses for state updates.
2. **Defer every view event inside expo-modules-core's
`ViewEvent`/`KEventEmitterWrapper`.** Rejected: changes event timing for
every Expo module view on Android, far beyond the reported bug; that is
a design decision for the maintainers.
3. **Route the event through `ShadowNodeProxy.scheduleFlush`'s pre-draw
mechanism.** Rejected: `scheduleFlush` holds a single pending closure
per view, so reusing it would make a size event and a style-size update
overwrite each other; a separate pre-draw listener duplicates machinery
that `post` already provides.
4. **Do nothing and wait for React Native's re-entrancy guard
([react/react-native#56311](react/react-native#56311
Rejected: the crash tears down production apps today, the RN PR is
unmerged, and dispatching JS events mid-measure stays a hazard for any
synchronous listener even with that guard.

</details>
<!-- /expo-bot:fix-options -->

---------

Co-authored-by: expo-bot <expo-bot@users.noreply.github.com>
Co-authored-by: nishan (o^▽^o) <nishanbende@gmail.com>
intergalacticspacehighway added a commit to expo/expo that referenced this pull request Oct 1, 2026
…performMeasureAndLayout called during measure layou (#49933)

> [!WARNING]
> **Agent-authored and NOT human-reviewed.** An automated `/verify
--fix` run for #47625 wrote this change and checked it in a sandbox; the
reasoning and evidence are in the outcome comment on that issue. Review
it as you would any external contribution.

Requested by @intergalacticspacehighway · [investigation
run](https://github.com/expo/expo/actions/runs/34403733608) · refs
#47625

Fixes #47625

`HostView` emits the `onLayoutContent` event synchronously from
Compose's `onSizeChanged` callback, which runs inside the measure pass.
A Fabric event listener that reacts synchronously —
react-native-reanimated's `onEventDispatch` hook — flushes mount items
on the same call stack and re-enters `onMeasure` of a view that is
mid-measure. Compose throws `performMeasureAndLayout called during
measure layout` and the ReactHost goes down (#47625).

This change posts the dispatch to the main looper, so the event fires
after the measure pass. `setStyleSize` timing does not change
(`ShadowNodeProxy.scheduleFlush` already defers it), so `matchContents`
sizing behaves as before.

Proof: the unpatched code crashed with the exact reported stack in 4 of
4 emulator stress runs (each within ~2–5 minutes), and a build with this
fix ran the same procedure for 784 cycles (~20 minutes) with no crash,
with `onLayoutContent` still firing.

<details><summary>Cause</summary>

Full chain, all in this repository at dcd4c59:

1. `ExpoView.requestLayout` posts `measureAndLayout`
([ExpoView.kt#L49-L55](https://github.com/expo/expo/blob/dcd4c59b5a78da5cbc43783dee5601e071c764af/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/ExpoView.kt#L49-L55)).
2. The measure pass reaches the Compose layout in
`MaybeMatchContentsLayout`, whose `Modifier.onSizeChanged` fires during
measure
([HostView.kt#L158-L162](https://github.com/expo/expo/blob/dcd4c59b5a78da5cbc43783dee5601e071c764af/packages/expo-ui/android/src/main/java/expo/modules/ui/HostView.kt#L158-L162))
and calls `dispatchOnLayoutContent`, which emits the event synchronously
([HostView.kt#L233](https://github.com/expo/expo/blob/dcd4c59b5a78da5cbc43783dee5601e071c764af/packages/expo-ui/android/src/main/java/expo/modules/ui/HostView.kt#L233)).
3. The emit goes through `KEventEmitterWrapper.emit(view, ...)` to
`FabricEventDispatcher.dispatchEvent`
([KModuleEventEmitterWrapper.kt#L103-L110](https://github.com/expo/expo/blob/dcd4c59b5a78da5cbc43783dee5601e071c764af/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/events/KModuleEventEmitterWrapper.kt#L103-L110))
— synchronous on the UI thread.
4. Reanimated's `NodesManager.onEventDispatch` runs
`performOperationsRespectingDrawPass` on the same stack. That guard only
skips the **draw** pass, not the measure pass, so it calls
`performOperations` → `FabricUIManager.scheduleMountItem`, which
executes queued mount items immediately on the UI thread.
`SurfaceMountingManager.updateLayout` then measures a `HostView` that is
still inside its own measure pass, and Compose throws.

`shadowNodeProxy.setStyleSize` / `setViewSize` are not part of the
hazard: `ShadowNodeProxy.scheduleFlush` already defers them to a
pre-draw listener plus a posted fallback
([ShadowNodeProxy.kt#L45-L73](https://github.com/expo/expo/blob/dcd4c59b5a78da5cbc43783dee5601e071c764af/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/views/ShadowNodeProxy.kt#L45-L73)),
and that deferral is present in every released 57.x (verified in the
57.0.2 npm tarball). The event emit at `HostView.kt#L233` is the one
remaining synchronous Fabric interaction inside the measure pass. The
synchronous Android dispatch shipped with
[#45776](#45776) (expo-ui 56.0.8) and
is carried by every released 57.x including 57.0.17.

</details>

<details><summary>Verification</summary>

Stress recipe on a hosted Android emulator (new architecture): two
screens cycled with `router.replace`; the hosts screen mounts 12 `<Host
matchContents>` with `Text` content changing every 35 ms plus a `<Host
matchContents={{vertical:true}}>` with `TextField`; 16 reanimated views
animate width/height continuously with `ReduceMotion.Never` (the
emulator's reduce-motion setting otherwise disables reanimated and
suppresses the repro); a busy-JS interval bunches React commits. App:
expo 57.0.13, react-native 0.86.2, reanimated 4.5.1, expo-router
57.0.13.

| Arm | Binary | @expo/ui native code | Result |
| --- | --- | --- | --- |
| Before (4 runs) | Expo Go, SDK 57 | bundled release, synchronous
dispatch | 4 of 4 runs crashed within ~2–5 min, exact reported stack |
| After (1 long run) | dev client, EAS build 3ff744d7 | 57.0.17 + this
fix, compiled from source | no crash through 784 cycles (~20 min);
`onLayoutContent` readout still updates |

One caveat, stated plainly: the before-arm binary is Expo Go and the
after-arm binary is a dev client, because the four EAS builds of an
unpatched dev client failed in Gradle before producing an artifact
(undiagnosed; this run's credential cannot read EAS build logs). The
crash mechanism lives in @expo/ui + React Native + reanimated code that
both binaries contain, and the same JS bundle and procedure drove both
arms. The crash-stack screenshots (attached to the linked findings
comment) show the full reported chain frame by frame, from
`dispatchOnLayoutContent-viCIZxY` through `onEventDispatch` and
`updateLayout` to the exception.

</details>

<details><summary>Checks run</summary>

In a sandbox checkout of this repository at dcd4c59 with the fix
applied, in `packages/expo-ui`: `pnpm run typecheck` (clean), `pnpm run
lint` (0 warnings, 0 errors), `pnpm test` (58 suites, 357 tests, 17
snapshots — all pass). Kotlin compile: `./gradlew
:expo-ui:compileDebugKotlin` with
`expo.autolinking.android.buildFromSource: ["expo-ui"]` succeeded on a
GitHub Actions ubuntu VM, with `expo-ui` listed without the `[📦]`
prebuilt marker (really compiled from source), and the patched file
compiled in the incremental rebuild. The EAS build 3ff744d7 that ran
on-device compiled the patched source the same way.

</details>

<details><summary>Not covered</summary>

- The `onSizeChanged` modifier in
[ModifierRegistry.kt#L695-L708](https://github.com/expo/expo/blob/dcd4c59b5a78da5cbc43783dee5601e071c764af/packages/expo-ui/android/src/main/java/expo/modules/ui/ModifierRegistry.kt#L695-L708)
dispatches the same way during measure. This change does not touch it; a
comment on #47625 reports a rarer residual crash after patching only
`HostView`, which could come from another synchronous dispatch site, but
no stack trace exists for it yet.
- iOS is untouched; the SwiftUI host does not share this code path.
- The deeper re-entrancy guard belongs to React Native
([react/react-native#56311](react/react-native#56311),
referenced from `ShadowNodeProxy`); this change removes expo-ui's own
trigger and does not wait for it.

</details>

<!-- expo-bot:fix-options v1 -->
<details><summary>Options considered</summary>

1. **Post the `onLayoutContent` dispatch (`post { ... }`) so it runs
after the measure pass.** Chosen: one line in `HostView.kt`;
`setStyleSize` timing is untouched, so `matchContents` sizing is
unchanged; the JS callback arrives one main-loop turn later, which its
documented contract ("triggered when the content completes its layout")
already allows. Matches the deferral pattern
`ShadowNodeProxy.scheduleFlush` already uses for state updates.
2. **Defer every view event inside expo-modules-core's
`ViewEvent`/`KEventEmitterWrapper`.** Rejected: changes event timing for
every Expo module view on Android, far beyond the reported bug; that is
a design decision for the maintainers.
3. **Route the event through `ShadowNodeProxy.scheduleFlush`'s pre-draw
mechanism.** Rejected: `scheduleFlush` holds a single pending closure
per view, so reusing it would make a size event and a style-size update
overwrite each other; a separate pre-draw listener duplicates machinery
that `post` already provides.
4. **Do nothing and wait for React Native's re-entrancy guard
([react/react-native#56311](react/react-native#56311
Rejected: the crash tears down production apps today, the RN PR is
unmerged, and dispatching JS events mid-measure stays a hazard for any
synchronous listener even with that guard.

</details>
<!-- /expo-bot:fix-options -->

---------

Co-authored-by: expo-bot <expo-bot@users.noreply.github.com>
Co-authored-by: nishan (o^▽^o) <nishanbende@gmail.com>
(cherry picked from commit b70dc5e)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants