Programmatic Control

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

GoalAPI
Start on an itemdefaultIndex
Move relativelynext / prev
Select an absolute itemscrollTo({ index })
Read settled selectiongetCurrentIndex()
React to settled selectiononSnapToItem
Drive UI-thread indicatorsprogress
Move content continuously at pixel levelscrollOffsetValue

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.