Skip to content

fix: reanimated 4.3.0 support and bugfixes#3532

Merged
isekovanic merged 6 commits intodevelopfrom
fix/bottom-sheet-modal-reanimated-inconsistencies
Apr 5, 2026
Merged

fix: reanimated 4.3.0 support and bugfixes#3532
isekovanic merged 6 commits intodevelopfrom
fix/bottom-sheet-modal-reanimated-inconsistencies

Conversation

@isekovanic
Copy link
Copy Markdown
Contributor

🎯 Goal

Summary

This PR upgrades examples/SampleApp to React Native 0.81.6, fixes the Metro setup needed for that upgrade, and addresses the two main animation regressions that showed up while validating the SDK against newer React Native / Reanimated behavior:

  • message overlay / context menu flicker during layout correction
  • the context menu completely falling apart on bottom sheet open
  • bottom sheet hard swipe close flicker

A big part of this work was understanding the impact of react-native-reanimated@4.3.0.

4.3.0 turned out to be a meaningful runtime change for this codebase, not just a routine version bump. In practice it changes the behavior of some animation paths that were previously stable on 4.2.3, especially around gesture driven transitions, overlay (modal specifically) post layout settle corrections and UI that depends on tight synchronization between layout and animated values.

During (yet another weekend) debugging, the reanimated static feature flags were part of the culprit. Namely, 4.3.0 enables some of the optional feature flags to be true by default. This unfortunately changes things in many ways:

  • FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS was the strongest flag level signal, as it directly messes with renders
  • enabling it on the older good baseline (4.2.3) was enough to reproduce the problematic behaviour
  • 4.3.0 still had regressions even with FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS=false, so the upgrade issue is broader than a single flag, but this flag was a major clue during the investigation

PS: The thing is breaks is opening the bottom sheet to full screen, while it has specifically a child FlatList that will change its content container size when opened to the highest snap point. This is noticeable immediately in the emoji picker (since it has a lot of items), but it will be the case for any of the sheets with enough content. Essentially, something in the commit flow of shared values (when the feature flag is enabled) changes the way other components behave when mounting new styles (i.e components with new animated styles) and all of this happening while other animations are going on. In the case of the FlatList, it's simply virtualization kicking in and destroying other styles entirely while there is an animation going on and items are being rendered. It boggles my mind that would affect the content below it and I was not able to find the culprit (yet), but for now we'll recommend to integrators that they do not use this feature flag specifically until either I open an issue /PR in the reanimated upstream or I find a good way to refactor our code so that this does not happen anymore. Needless to say, these issues are ridiculously difficult to debug and even more difficult to reproduce efficiently.

Changes:

SampleApp upgrade

  • Upgraded examples/SampleApp to React Native 0.81.6
  • Updated the aligned native/runtime dependencies needed for the RN 0.81 baseline
  • Fixed the SampleApp Metro config so the app can boot correctly on the upgraded stack

Message overlay / context menu

  • Refactored MessageOverlayHostLayer so overlay pieces no longer split their vertical position across absolute top and animated translateY
  • Each overlay layer now animates a single corrected Y value and applies it through absolute positioning
    • This was the real kicker on 4.3.0, as having multiple animated styles and expecting them to play nice with each other is out of question

This fixes the visible one frame message/context-menu flicker that could happen during open time layout corrections, especially on newer Reanimated versions. This was not an issue with 4.3.0 specifically either.

Bottom sheet

  • Kept the sheet on the transform based motion path, but with a huge overhaul (almost a rewrite), to facilitate animating through a logical state machine that does not imply the need to cancelAnimation
  • Fixed the hard swipe-to-close flicker by separating gesture finalization from the close timing animation by one frame (even though the shared values were kept intact, the animation would very briefly restart on hard close and moving this orchestration to the JS stack helps tremendously as we no longer depend layout commit topology on the reanimated stack to make sure that we are doing stuff right)
  • Kept keyboard offset composed into the final sheet transform instead of letting keyboard handling fight the sheet motion path
  • Fixed the bottom sheet opening for only 2-3 frames and then freezing, often not accepting gestures either
    • This was fixed by automation with the cancelAnimation fixes, but the deal is that now the order of operations within reanimated has changed and also having unsafe behaviour like we did really messes with the new SharedValue architecture they've included in 4.3.0

This fixes the brief top-snap flash that could happen when closing the sheet with a fast downward gesture.

Most of the work here was not about changing product behaviour intentionally, but about making the SDK’s animation model more robust against newer React Native/reanimated timing and commit behaviour.

The two concrete fixes were:

  • stop splitting overlay Y position across multiple visual channels
  • stop handing bottom-sheet gesture motion directly into close timing in the same frame

The changes have been extensively tested on both 4.3.0 and pre-4.3.0. This is of course with FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS disabled, as I have no clue why it breaks stuff so much. More thorough investigation into that will definitely be done in the future.

🛠 Implementation details

🎨 UI Changes

iOS
Before After
Android
Before After

🧪 Testing

☑️ Checklist

  • I have signed the Stream CLA (required)
  • PR targets the develop branch
  • Documentation is updated
  • New code is tested in main example apps, including all possible scenarios
    • SampleApp iOS and Android
    • Expo iOS and Android

@isekovanic isekovanic requested a review from oliverlaz April 5, 2026 03:06
hasCommittedVisibilityRef.current = true;
wasVisibleRef.current = visible;

if (!visible || wasVisible) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Something this silly helps with at least 2-3 edge cases here. Interesting !

return solvedBottomTop - bottomH.value.y;
});

useAnimatedReaction(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note for myself: These need to be extracted in a helper or something that will improve reusability.

@Stream-SDK-Bot
Copy link
Copy Markdown
Contributor

Stream-SDK-Bot commented Apr 5, 2026

SDK Size

title develop branch diff status
js_bundle_size 348 KB 348 KB 0 B 🟢

position: 'absolute',
top: topH.value.y,
top: topVisualY.value,
opacity: 1,
Copy link
Copy Markdown
Contributor Author

@isekovanic isekovanic Apr 5, 2026

Choose a reason for hiding this comment

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

this trick is being done because we can only layout the top/bottom items absolutely once we know their layout, meaning that there's a good chance on slower devices that they'll get teleported before that and basically have no positional props (and this would cause the teleported components to be rendered for 1 frame somewhere on the screen, typically top-left as the absolute fill system doesn't know what to do with them)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

so, we essentially do this:

  • render & teleport
  • layout "invisibly"
  • wait for onLayout to trigger
  • update the reanimated mutables
  • only then change the items to "visible"


const sheetAnimatedStyle = useAnimatedStyle(() => ({
transform: [{ translateY: translateY.value }],
paddingBottom: translateY.value,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

with this rewrite, we no longer need to apply paddingBottom for the content to flow properly - but rather can rely purely on translations, which is a nice little perf boost

@isekovanic isekovanic merged commit fdb3e56 into develop Apr 5, 2026
5 checks passed
@isekovanic isekovanic deleted the fix/bottom-sheet-modal-reanimated-inconsistencies branch April 5, 2026 03:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants