React Native Reanimated in react-native-reanimated-carousel#
The carousel library is built on top of React Native Reanimated and uses its primitives — useSharedValue, useAnimatedReaction, useAnimatedStyle, interpolate, and related APIs — as the foundation for all animation state, gesture tracking, and pagination rendering. As of v5.x, Reanimated 4.1+ is required, with react-native-worklets as a separate peer dependency.
Key Reanimated Concepts Used#
| Concept | Role in the carousel |
|---|---|
useSharedValue | Holds mutable animated state (offset, size, index, progress) that lives on the UI thread |
useAnimatedReaction | Reacts to shared value changes inside worklets; drives side-effects without touching React state directly |
useAnimatedStyle | Derives per-item visual styles (position, opacity, scale) from shared values |
interpolate / interpolateColor | Translates raw offset/progress values into visual properties |
scheduleOnRN (from react-native-worklets) | Safely schedules JS-thread callbacks from worklets, replacing the older runOnJS pattern |
Shared Values and Animation State#
The library's animation state is managed through a network of shared values. The primary ones are created in useCommonVariables.ts:
_handlerOffset— the raw scroll translation (in pixels), updated by gesture handlersresolvedSize— the computed page size (width or height depending on axis)isMoving,sizePhase,hasInitializedOffset— lifecycle flags
The consumer-facing shared value is progress (type SharedValue<number>), representing the carousel's absolute page index (0, 1, 2, …). Consumers create it with useSharedValue<number>(0) and pass it directly to the onProgressChange prop :
const progress = useSharedValue<number>(0);
// ...
<Carousel onProgressChange={progress} ... />
<Pagination.Basic progress={progress} ... />
The optional scrollOffsetValue prop (replacing the deprecated defaultScrollOffsetValue) exposes the raw pixel offset for advanced consumers .
useAnimatedReaction Usage Patterns#
useAnimatedReaction is the primary reactive primitive across the codebase. It follows a consistent three-argument pattern: a selector worklet, an effect worklet, and a dependency array.
Progress tracking — useOnProgressChange.ts is the canonical example. It watches handlerOffset → computes logical progress via getLogicalProgress → writes to the progress SharedValue and, if a callback was provided, fires it on the JS thread via scheduleOnRN .
Index tracking — useCarouselController.tsx uses useAnimatedReaction to derive physicalIndex and rawIndex from the handler offset, updating the index SharedValue and calling setLiveRawIndex via scheduleOnRN.
Pagination state — PaginationItem.tsx watches whether the current dot is "selected" and updates a local React useState variable via scheduleOnRN(setSelected, nextSelected), which then drives accessibilityState.selected.
Other files using this pattern include CarouselLayout.tsx (autoplay readiness), ItemLayout.tsx (per-item accessibility), ItemRenderer.tsx (visible range updates), and ScrollViewGesture.tsx (boundary resets).
Strict Mode Compliance: No SharedValue Reads During Render#
Reanimated v4 enforces that SharedValue.value must not be read during a React render phase. Doing so triggers:
WARN [Reanimated] Reading from `value` during component render.
Root cause (issue #861 / #940): PaginationItem was reading progress.value directly during render to determine the accessibilityState.selected prop .
Fix (PR #866 / #944): The selected state was split into a React useState, initialized with a lazy initializer (useState(() => expression)), and then kept in sync through useAnimatedReaction + scheduleOnRN . The lazy initializer ensures React only evaluates the expression on first mount, which is exempt from Reanimated's strict check. A plain expression would be re-evaluated on every parent-driven re-render, triggering the warning .
The general rule the library follows: read SharedValue.value only inside useAnimatedReaction selectors, useAnimatedStyle, or other worklet contexts — never in component render logic or event handlers.
scheduleOnRN vs. runOnJS#
Prior to Reanimated 4, runOnJS was the standard way to call JS-thread functions from worklets. In v5.x of the carousel, runOnJS is not used; all cross-thread calls go through scheduleOnRN from react-native-worklets . Reanimated still re-exports runOnJS for backward compatibility, but new code should use scheduleOnRN.
Key Source References#
| File | Purpose |
|---|---|
src/hooks/useOnProgressChange.ts | Canonical useAnimatedReaction + scheduleOnRN pattern |
src/hooks/useCommonVariables.ts | Core shared value creation |
src/hooks/useCarouselController.tsx | Index tracking via useAnimatedReaction |
src/components/Pagination/PaginationItem.tsx | Strict-mode-compliant pagination state sync |
| PR #866 | Fix for render-phase SharedValue reads |
| Migration Guide to v5.x | Reanimated 4 + Worklets upgrade instructions |