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!
π 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' },
});
π 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;
π₯ 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,
},
});
π 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',
},
});
π 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,
},
});
β€οΈ 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,
},
});
π¬ 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)