Programmatic Control
Carousel remains uncontrolled: there is no controlled index prop. Use defaultIndex for the mount position and CarouselRef for later navigation.
Setup
import * as React from "react";
import {
Carousel,
type CarouselRef,
} from "react-native-reanimated-carousel";
function ProductCarousel() {
const ref = React.useRef<CarouselRef>(null);
return (
<Carousel
ref={ref}
data={products}
keyExtractor={(product) => product.id}
renderItem={({ item }) => <ProductCard product={item} />}
/>
);
}next and prev
Use these methods for relative movement:
ref.current?.next();
ref.current?.next({ count: 3 });
ref.current?.prev({ animated: false });
ref.current?.prev({ count: 2, animated: true });interface CarouselStepOptions {
count?: number; // positive integer, default 1
animated?: boolean; // default true
}count={0} is a no-op. Other invalid counts warn once per Carousel instance in development and are ignored.
With loop={false}, a movement that would cross a boundary stops at that boundary. With loop, next and prev continue in the requested logical raw-data direction across cycles.
scrollTo
scrollTo performs absolute raw-data index navigation:
ref.current?.scrollTo({ index: 3 });
ref.current?.scrollTo({ index: 0, animated: false });interface CarouselScrollToOptions {
index: number; // required in-range raw-data index
animated?: boolean; // default true
}An invalid index warns once per Carousel instance in development and does nothing. In loop mode, the command chooses the shortest route; an exact distance tie chooses logical forward.
Relative scrollTo({ count }) and command onFinished callbacks were removed in v5. Use next/prev for relative movement and onSnapToItem for settled selection.
getCurrentIndex
const index = ref.current?.getCurrentIndex();The result is the last settled raw-data index:
- during a gesture or animation, it remains the previous settled index;
- an accepted non-animated command updates it immediately;
- after settle, it matches the argument passed to
onSnapToItem; - for empty data, it returns the neutral value
0.
Read getCurrentIndex() from an event handler or effect, not during render. The ref is an imperative snapshot and does not trigger React rendering.
Lifecycle
<Carousel
ref={ref}
data={products}
renderItem={renderProduct}
onScrollStart={() => {
console.log("movement started");
}}
onSnapToItem={(index) => {
console.log("settled on", index);
}}
/>An accepted gesture, command, or autoplay transition fires onScrollStart once. Settle fires onSnapToItem, including when a gesture returns to the same index. An accepted non-animated command emits both synchronously in that order.
Rejected/no-op commands, relayout, data reconciliation, and direct scrollOffsetValue writes do not fire these callbacks.
Because autoplay also fires onScrollStart, do not use it as a touch detector. Observe onBegin through onConfigurePanGesture when user contact matters.
Pagination control
import * as React from "react";
import { View } from "react-native";
import { useSharedValue } from "react-native-reanimated";
import {
Carousel,
Pagination,
type CarouselRef,
} from "react-native-reanimated-carousel";
function CarouselWithPagination() {
const ref = React.useRef<CarouselRef>(null);
const progress = useSharedValue(0);
const data = ["One", "Two", "Three"];
return (
<View>
<Carousel
ref={ref}
data={data}
progress={progress}
renderItem={({ item }) => <Slide title={item} />}
/>
<Pagination
count={data.length}
progress={progress}
onPress={(index) => ref.current?.scrollTo({ index })}
/>
</View>
);
}Pagination receives raw-data indices and never owns the ref or writes progress.
Data updates
Use keyExtractor when the current item should survive insertions and reordering:
<Carousel
ref={ref}
data={products}
keyExtractor={(product) => product.id}
renderItem={renderProduct}
/>If the selected key still exists, it remains selected. If it disappears—or no keyExtractor is provided—the old numeric index is clamped into the new range. A data update during movement is reconciled after settle without lifecycle callbacks.
If your application adds an item and wants to select it, navigate after React commits the new data:
const [pendingIndex, setPendingIndex] = React.useState<number | null>(null);
function addProduct(product: Product) {
setProducts((current) => {
setPendingIndex(current.length);
return [...current, product];
});
}
React.useEffect(() => {
if (pendingIndex === null || pendingIndex >= products.length) return;
ref.current?.scrollTo({ index: pendingIndex });
setPendingIndex(null);
}, [pendingIndex, products.length]);Choosing the right control surface
| Goal | API |
|---|---|
| Start on an item | defaultIndex |
| Move relatively | next / prev |
| Select an absolute item | scrollTo({ index }) |
| Read settled selection | getCurrentIndex() |
| React to settled selection | onSnapToItem |
| Drive UI-thread indicators | progress |
| Move content continuously at pixel level | scrollOffsetValue |
Directly writing scrollOffsetValue is advanced two-way control. It moves content but does not commit selection; finish with scrollTo({ index }) when a semantic selected item is required.