DEV Community

Cover image for Building a Reels UI in React Native πŸŽ₯ | Smooth Scrolling, Auto-Playing Videos & More!
Amit Kumar
Amit Kumar

Posted on

Building a Reels UI in React Native πŸŽ₯ | Smooth Scrolling, Auto-Playing Videos & More!

Reels-style short videos have taken social media by storm! Want to build your own? In this guide, we’ll create a seamless vertical video feed with auto-play, smooth scrolling, and interactive UI elementsβ€”just like Instagram Reels or TikTok!

Image description

🌟 What We'll Build
A dynamic Reels UI with:
βœ… Auto-playing videos when visible
βœ… Smooth vertical scrolling with Animated.FlatList
βœ… Engaging UI elements (like, comment, share buttons)
βœ… Optimized playback performance


πŸ›  Step-by-Step Implementation

πŸ“Œ 1. ReelComponent: The Video Feed
Manages scrolling, video playback visibility, and rendering video rows dynamically.

import { Animated, StyleSheet, View, useWindowDimensions } from 'react-native';
import React, { useRef, useState, useCallback } from 'react';
import { VIDEO_DATA } from './data';
import FeedRow from './FeedRow';

const ReelComponent = () => {
  const {height} = useWindowDimensions();
  const scrollY = useRef(new Animated.Value(0)).current;
  const [scrollInfo, setScrollInfo] = useState({isViewable: true, index: 0});
  const refFlatList = useRef(null);

  const viewabilityConfig = useRef({viewAreaCoveragePercentThreshold: 80});

  const onViewableItemsChanged = useCallback(({changed}) => {
    if (changed.length > 0) {
      setScrollInfo({
        isViewable: changed[0].isViewable,
        index: changed[0].index,
      });
    }
  }, []);

  const getItemLayout = useCallback(
    (_, index) => ({
      length: height,
      offset: height * index,
      index,
    }),
    [height],
  );

  const keyExtractor = useCallback(item => `${item.id}`, []);

  const onScroll = useCallback(
    Animated.event([{nativeEvent: {contentOffset: {y: scrollY}}}], {
      useNativeDriver: true,
    }),
    [],
  );

  const renderItem = useCallback(
    ({item, index}) => {
      const {index: scrollIndex} = scrollInfo;
      const isNext = Math.abs(index - scrollIndex) <= 1;

      return (
        <FeedRow
          data={item}
          index={index}
          isNext={isNext}
          visible={scrollInfo}
          isVisible={scrollIndex === index}
        />
      );
    },
    [scrollInfo],
  );
  return (
    <View style={styles.flexContainer}>
      <StatusBar barStyle={'light-content'} backgroundColor={'black'} />
      <Animated.FlatList
        pagingEnabled
        showsVerticalScrollIndicator={false}
        ref={refFlatList}
        automaticallyAdjustContentInsets
        onViewableItemsChanged={onViewableItemsChanged}
        viewabilityConfig={viewabilityConfig.current}
        onScroll={onScroll}
        data={VIDEO_DATA}
        renderItem={renderItem}
        getItemLayout={getItemLayout}
        decelerationRate="fast"
        keyExtractor={keyExtractor}
        onEndReachedThreshold={0.2}
        removeClippedSubviews
        bounces={false}
      />
    </View>
  );
};

export default ReelComponent;

const styles = StyleSheet.create({
  flexContainer: { flex: 1, backgroundColor: 'black' },
});

Enter fullscreen mode Exit fullscreen mode

🎞 2. FeedRow: Handling Individual Videos

Each video component, along with sidebars and footers, is wrapped inside FeedRow.

import { StyleSheet, View } from 'react-native';
import React from 'react';
import VideoComponent from './VideoComponent';
import FeedFooter from './FeedFooter';
import FeedSideBar from './FeedSideBar';
import FeedHeader from './FeedHeader';

const FeedRow = ({data, index, visible, isVisible, isNext}) => {
  return (
    <View>
      <VideoComponent data={data} isNext={isNext} isVisible {isVisible} />
      <FeedHeader index={index} />
      <FeedSideBar data={data} />
      <FeedFooter data={data} />
    </View>
  );
};

export default FeedRow;

Enter fullscreen mode Exit fullscreen mode

πŸŽ₯ 3. VideoComponent: Auto-Playing Video Logic

Ensures smooth playback by muting & pausing videos when out of view.

import { StyleSheet, useWindowDimensions } from 'react-native';
import React, { useMemo } from 'react';
import Video from 'react-native-video';

const VideoComponent = ({data, isVisible}) => {
  const {height} = useWindowDimensions();

  const videoStyle = useMemo(() => styles.video(height), [height]);

  return (
    <>
      <Video
        source={{uri: data.video}}
        autoPlay
        repeat
        resizeMode="cover"
        muted={!isVisible}
        playInBackground={false}
        paused={!isVisible}
        ignoreSilentSwitch="ignore"
        style={videoStyle}
      />
      <LinearGradient
        colors={[
          '#000000F0',
          '#000000D0',
          '#000000A0',
          '#00000070',
          '#00000040',
        ]}
        start={{x: 0, y: 0}}
        end={{x: 0, y: 0.5}}
        style={styles.controlsContainer}
      />
    </>
  );
};

export default VideoComponent;

const styles = StyleSheet.create({
  video: height => ({
    backgroundColor: 'black',
    width: '100%',
    height: Platform.OS === 'ios' ? height : height - 50,
  }),
  controlsContainer: {
    ...StyleSheet.absoluteFillObject,
  },
});

Enter fullscreen mode Exit fullscreen mode

🎭 4. UI Components: Making It Engaging

🏷 FeedHeader: The Title & Camera Icon

import { SafeAreaView, StyleSheet, Text } from 'react-native';
import React from 'react';
import { CameraIcon } from '../../assets';

const FeedHeader = ({ index }) => {
  return (
    <SafeAreaView style={styles.container}>
      {index === 0 && <Text style={styles.title}>Reels</Text>}
      <CameraIcon />
    </SafeAreaView>
  );
};

export default FeedHeader;

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    alignItems: 'center',
    position: 'absolute',
    top: Platform.OS === 'ios' ? 65 : 10,
    marginHorizontal: 20,
  },
  alignRight: {
    alignSelf: 'flex-end',
    right: 5,
  },
  title: {
    color: '#fff',
    flex: 1,
    fontSize: 24,
    fontWeight: '700',
  },
});
Enter fullscreen mode Exit fullscreen mode

🎭 5. UI Components: Making It Engaging

🏷 FeedFooter

const FeedFooter = ({ data }) => {
  const { thumbnailUrl, title, description, isLive, friends } = data;
  const followerCount = Math.floor(Math.random() * 20) + 1;

  return (
    <View style={styles.container}>
      <View style={styles.profileContainer}>
        <Image source={{ uri: thumbnailUrl }} style={styles.thumbnail} resizeMode="cover" />
        <View style={styles.userInfo}>
          <View style={styles.userNameContainer}>
            <Text style={styles.nameStyle}>{title}</Text>
            {isLive && <TickIcon />}
          </View>
          <View style={styles.audioContainer}>
            <MusicIcon width={10} height={10} />
            <Text style={styles.audioText}>Original audio</Text>
          </View>
        </View>
        <View style={styles.followButton}>
          <Text style={styles.followText}>Follow</Text>
        </View>
      </View>

      <Text numberOfLines={2} style={styles.desc}>
        {description}
      </Text>

      <View style={styles.friendsContainer}>
        {friends.map((item, index) => (
          <Image key={index} source={{ uri: item.imageUrl }} style={styles.friendImage} />
        ))}
        <Text style={styles.followInfo}>{`Followed by Akash and ${followerCount} others`}</Text>
      </View>
    </View>
  );
};

export default FeedFooter;


const styles = StyleSheet.create({
  container: {
    position: 'absolute',
    bottom: Platform.OS === 'ios' ? 120 : 90,
    marginLeft: 20,
  },
  profileContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 12,
  },
  thumbnail: {
    width: 30,
    height: 30,
    borderRadius: 20,
    overflow: 'hidden',
  },
  userInfo: {
    marginLeft: 10,
  },
  userNameContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  nameStyle: {
    color: '#fff',
    fontSize: 12,
    fontWeight: '700',
    marginRight: 4,
  },
  audioContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    marginTop: 2,
  },
  audioText: {
    color: '#fff',
    marginLeft: 6,
  },
  followButton: {
    marginLeft: 24,
    borderWidth: 1,
    borderColor: '#fff',
    borderRadius: 8,
    paddingHorizontal: 8,
    paddingVertical: 2,
  },
  followText: {
    color: '#fff',
  },
  desc: {
    color: '#fff',
    width: 300,
  },
  friendsContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    marginTop: 10,
  },
  friendImage: {
    width: 15,
    height: 15,
    borderRadius: 150,
    marginRight: -5,
  },
  followInfo: {
    color: '#fff',
    marginLeft: 13,
    fontSize: 12,
  },
});
Enter fullscreen mode Exit fullscreen mode

❀️ FeedSideBar: Like, Comment, and Share Buttons

const IconWithText = ({IconComponent, count}) => (
  <View style={styles.iconContainer}>
    <IconComponent />
    <Text style={styles.countText}>{count}</Text>
  </View>
);

const FeedSideBar = ({data}) => {
  const {likes, comments, shares, thumbnailUrl} = data;

  return (
    <View style={styles.container}>
      <IconWithText IconComponent={HeartIcon} count={likes} />
      <IconWithText IconComponent={CommentIcon} count={comments} />
      <IconWithText IconComponent={ShareIcon} count={shares} />
      <MenuIcon />
      <View style={styles.thumbnailContainer}>
        <Image
          source={{uri: thumbnailUrl}}
          style={styles.thumbnail}
          resizeMode="cover"
        />
      </View>
    </View>
  );
};

export default FeedSideBar;


const styles = StyleSheet.create({
  container: {
    position: 'absolute',
    bottom: Platform.OS === 'ios' ? 120 : 90,
    alignSelf: 'flex-end',
    alignItems: 'center',
    gap: 20,
    right: 20,
  },
  iconContainer: {
    alignItems: 'center',
  },
  countText: {
    color: '#fff',
    marginTop: 10,
  },
  thumbnailContainer: {
    borderWidth: 3,
    borderColor: '#fff',
    borderRadius: 8,
    overflow: 'hidden',
  },
  thumbnail: {
    width: 24,
    height: 24,
    borderRadius: 8,
  },
});

Enter fullscreen mode Exit fullscreen mode

🎬 Final Thoughts

Congratulations! πŸŽ‰ You’ve built a sleek, high-performance Reels UI in React Native. With auto-playing videos, smooth scrolling, and interactive UI elements, this implementation is perfect for any social media or short-video app.

πŸš€ Ready to take it further? Try adding:
πŸ”₯ Swipe gestures for seamless navigation
πŸ“Œ Caching videos for smoother playback
🎢 Background music support

Now go build your own viral Reels app! πŸš€


πŸ’‘ Got questions or improvements? Drop a comment below! πŸ’¬βœ¨

Top comments (0)