How to Build a Slider in React Native Using FlatList

How to Build a Slider in React Native Using FlatList

When implementing a slider in React Native, many developers instinctively turn to external libraries, assuming that creating a slider from scratch is a complex task. However, what may seem like a daunting challenge is actually much simpler to accomplish in React Native using its built-in components—often more straightforward than implementing a similar feature on the web.

So, how can you build a slider without relying on third-party libraries?

In this tutorial, I'll show you how to create a fully functional slider in React Native using just FlatList with minimal code. It's an easy and effective approach that leverages React Native's native components to achieve smooth, efficient sliders.

We are going to create this style of slider

Creating the item component

import { View, Text, StyleSheet, Image } from "react-native";

interface OnboardingCardProps {
  imageUrl: string;
  title: string;
  description: string;
  width: number;
}

const OnboardingCard = ({
  imageUrl,
  title,
  description,
  width,
}: OnboardingCardProps) => {
  return (
    <View style={[styles.container, { width }]}>
      <Image source={{ uri: imageUrl }} style={styles.image} />

      <Text style={styles.title}>{title}</Text>

      <Text style={styles.subtitle}>{description}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    padding: 20,
    backgroundColor: "#fff",
  },
  image: {
    width: 150,
    height: 150,
    resizeMode: "contain",
    marginBottom: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: "bold",
    color: "#000",
    textAlign: "center",
    marginBottom: 10,
  },
  subtitle: {
    fontSize: 16,
    color: "#666",
    textAlign: "center",
    paddingHorizontal: 20,
  },
});

export default OnboardingCard;

Creating the slider

import { Dimensions, FlatList, StyleSheet } from "react-native";
import { onboardingSteps } from "data/onboarding";
import OnboardingCard from "components/onboarding/OnboardingCard";

const screenWidth = Math.round(Dimensions.get("window").width);

const Onboarding = () => {
  return (
    <FlatList
      style={styles.slider}
      horizontal
      data={onboardingSteps}
      showsHorizontalScrollIndicator={false}
      contentContainerStyle={styles.sliderItemsWrapper}
      pagingEnabled
      renderItem={({ item }) => (
        <OnboardingCard
          width={screenWidth}
          title={t(item.title)}
          description={t(item.description)}
          image={item.image}
        />
      )}
    />
  );
};

export default Onboarding;

const styles = StyleSheet.create({
  viewport: {
    flex: 1,
  },
  wrapper: {
    flex: 1,
  },
  slider: {
    flex: 1,
  },
  sliderItemsWrapper: {
    display: "flex",
    alignItems: "center",
  },
});

Explaining the props used on the FlatList

  • horizontal prop allow us to have items aligned horizontally

  • The pagingEnabled prop is what creates the “snapping” effect in a scrollable view. When this prop is set to true, the user can only scroll one full page (or item) at a time, causing the scroll to “snap” to the nearest page when they release their finger. This means that partial views of items won't be left visible; the scroll will always land in such a way that one full item is shown, creating a smooth, page-by-page scrolling experience.

  • Without it, here is how the scroll goes

    But with it, we got this

  • showsHorizontalScrollIndicator is to hide the scroll indication

💡
To show only one item at a time, make sure both the FlatList and its items have the same width.

Adding the indicators

Dot component ( the dot item component )

import { StyleSheet, View } from "react-native";

interface DotProps {
  active: boolean;
}

const Dot = ({ active }: DotProps) => {
  return <View style={[styles.dot, ...(active ? [styles.active] : [])]} />;
};

export default Dot;

const styles = StyleSheet.create({
  dot: {
    backgroundColor: "#D3BDF2",
    borderRadius: 50,
    marginHorizontal: 5,
    width: 10,
    height: 10,
  },
  active: {
    width: 16,
    height: 16,
    backgroundColor: "#6A3EFC",
  },
});

The dots list component

import { StyleSheet, View } from "react-native";
import Dot from "./Dot";

interface DotIndicatorsProps {
  itemCount: number;
  activeIndex: number;
}
const DotIndicators = ({ activeIndex, itemCount }: DotIndicatorsProps) => {
  return (
    <View style={styles.container}>
      {Array.from({ length: itemCount }).map((_, index) => (
        <Dot active={activeIndex === index} />
      ))}
    </View>
  );
};

export default DotIndicators;

const styles = StyleSheet.create({
  container: {
    flexDirection: "row",
    justifyContent: "center",
    alignItems: "center",
  },
});

Creating a state for the current item

Storing the current active index into a state and display the dots indicators.

import { Dimensions, FlatList, StyleSheet } from "react-native";
import { onboardingSteps } from "data/onboarding";
import OnboardingCard from "components/onboarding/OnboardingCard";

const screenWidth = Math.round(Dimensions.get("window").width);

const Onboarding = () => {
  const [currentIndex, setCurrentIndex] = useState(0);

  return (
    <>
        <FlatList
          // ...
          viewabilityConfig={{ viewAreaCoveragePercentThreshold: 100 }}
          onViewableItemsChanged={({ viewableItems, changed }) => {
                const currentIndex = viewableItems.at(0)?.index;
                if (currentIndex !== null && currentIndex !== undefined) {
                  setCurrentIndex(currentIndex);
                }
          }}
        />

        <DotIndicators
           itemCount={onboardingSteps.length}
           activeIndex={currentIndex}
        />
    </>
  );
};

The viewabilityConfig.viewAreaCoveragePercentThreshold prop determines the percentage of an item that must be visible for it to be considered "viewable" within the viewport. By setting this value to 100, the item will only be considered "viewable" when it is fully visible (i.e., 100% of the item's area is visible on the screen).

This ensures that the current index calculation is more accurate, particularly when the user is scrolling slowly. Without this setting, items that are only partially visible could be incorrectly marked as "viewable," causing the currentIndex to update prematurely or inaccurately. By enforcing the requirement that an item must be fully visible, you prevent incorrect index changes that can occur when the user is scrolling slowly and only small parts of items are entering or exiting the viewport.

In short, setting the viewAreaCoveragePercentThreshold to 100% ensures that the current item index only changes when the item is fully in view, leading to more precise behavior during slow scrolling.

The result

Conclusion

You only need three props to create a slider in React Native without external libraries:

  • pagingEnabled: Enables the snap effect for smooth scrolling.

  • onViewableItemsChanged: Tracks the active item as the user scrolls.

  • viewabilityConfig.viewAreaCoveragePercentThreshold: Ensures accurate active index calculation by requiring 100% of the item to be visible.

With just these props, you can easily create a slider using React Native's built-in components.

Resources