DEV Community

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

Posted on

4 2 1 1 1

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! πŸ’¬βœ¨

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Image of Docusign

πŸ› οΈ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more