Skip to content

fix: support reanimated 4 and add renderWindow for long carousels - #32

Merged
petrkonecny2 merged 4 commits into
mainfrom
fix/reanimated-4-and-render-window
Oct 10, 2026
Merged

petrkonecny2 merged 4 commits into
mainfrom
fix/reanimated-4-and-render-window

Conversation

@petrkonecny2

@petrkonecny2 petrkonecny2 commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Two changes, one commit each.

fix: stop worklets capturing React elements. On Reanimated 4 the carousel crashes on mount in development with [Worklets] Cannot copy value of type FiberNode. Reanimated 4 copies everything a worklet captures to the UI thread, and three worklets captured React elements:

  • AnimatedPagedView: the pan gesture read props.onScrollBeginDrag and the scroll reaction read props.onScroll, which captures all of props, including children. clampValue read childrenArray.length, which captures the array.
  • useInfiniteScroll: the reaction read paddedChildrenArray.length. It runs even with infinite scroll disabled.

They now capture only the two callbacks and the slide count. Behaviour is unchanged.

perf: add renderWindow to the provider. Every slide stays mounted, so a long carousel pays for all of them on mount and runs every slide's animated styles (useInterpolateInsideCarousel, ItemAnimatedView) on each frame of a swipe. With renderWindow={2}, only the current slide and two on each side render their content. The others are empty slots of the same width, so positions, snapping and goToPage don't change.

  • Opt-in: without the prop every slide stays mounted. Unmounting drops slide state, such as video playback, so existing carousels see no change.
  • Re-renders: the current page reaches React only when the rounded page changes, through useAnimatedReaction, not on every frame. HeroCarouselSlide is memoized, so only slides entering or leaving the window re-render.
  • Infinite scroll: the window is measured between real slides and wraps around. When the carousel jumps from the clone at the end back to the real first slide, that slide is already mounted, so there's no empty frame. The logic is in utils/isInRenderWindow.ts with unit tests.

Documented in the README provider table.

Testing

  • type:check, lint, test (28 passing, 5 new) and Prettier.
  • The worklet fix in an Expo SDK 57 / RN 0.86 / Reanimated 4.5.1 app on the iOS simulator: before, the carousel crashed on mount; after, it renders, swipes both ways and passes taps through to the slides.
  • renderWindow is covered by the unit tests only. I haven't run it in an app with a long carousel yet, so the example app's carousels are worth a quick run with renderWindow set, especially with infinite scroll.

Related Tickets/Issues

Found while trying the library for the parishes carousel in the Ascension app.

🤖 Generated with Claude Code

📦 Published PR as canary version: 0.1.15--canary.32.a9779c8.0

✨ Test out this PR locally via:

npm install @strv/react-native-hero-carousel@0.1.15--canary.32.a9779c8.0
# or 
yarn add @strv/react-native-hero-carousel@0.1.15--canary.32.a9779c8.0

Petr Konecny and others added 2 commits October 10, 2026 18:18
The pan gesture, the scroll reaction and the infinite-scroll reaction read from props and the children array inside worklets. Reanimated 4 copies everything a worklet captures to the UI thread and throws on React elements in development (Cannot copy value of type FiberNode), so the carousel crashed on mount. They now capture only the callbacks and the slide count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every slide stayed mounted, so a long carousel paid for all of them on mount and ran every slide's animated styles on each frame. With renderWindow, slides further away render as empty slots of the same width. It's opt-in, since unmounting drops slide state such as video playback. With infinite scroll the window wraps around, so the slide a clone jumps to is already mounted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@petrkonecny2
petrkonecny2 merged commit 9e6bd2f into main Oct 10, 2026
3 checks passed

This branch was successfully deployed

1 active deployment
npm — a9779c8c Deployed Oct 10, 2026 by petrkonecny2 via Publish #52
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.

1 participant