DEV Community

Reinaldo Simoes
Reinaldo Simoes

Posted on

2 2 2 1 1

Building a TikTok-Style Vertical Video Feed with React

When I needed a smooth, high-performance vertical video feed in a React web project, I was surprised by how few complete solutions existed.

I found some great inspiration from this awesome React Native tutorial by @albertocabrerajr, which showed how to build a TikTok-style feed in React Native with Expo.

But for React DOM (the web) —

  • Most examples were for mobile native apps.
  • Many web versions lacked smooth auto-play, visibility handling, or performance optimizations.
  • Others were just CSS tricks without real video lifecycle control.

So, I decided to build something from scratch, tailored for the web:
react-vertical-feed — a clean, optimized, developer-friendly vertical video feed component for React web apps.

✨ Meet react-vertical-feed

react-vertical-feed is a lightweight React component that solves the key challenges of building a TikTok-style vertical video experience on the web.

It handles:

  • Smooth vertical scrolling
  • Automatic play and pause based on video visibility
  • Lazy loading and resource management
  • Cross-browser compatibility
  • Performance optimizations for buttery smooth scrolling

🔥 How It Works

1. Video Visibility with Intersection Observer

The component uses the Intersection Observer API to detect which video is visible, and automatically play or pause it.

useEffect(() => {
  const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
      const index = parseInt(entry.target.getAttribute('data-index') || '0', 10);
      const item = items[index];

      const video = entry.target.querySelector('video') as HTMLVideoElement;
      if (entry.isIntersecting) {
        video?.play().catch(console.error);
        onItemVisible?.(item, index);
      } else {
        video?.pause();
        onItemHidden?.(item, index);
      }
    });
  }, { threshold });

  const mediaElements = containerRef.current?.querySelectorAll('[data-index]') || [];
  mediaElements.forEach(media => observer.observe(media));

  return () => observer.disconnect();
}, [items, onItemVisible, onItemHidden, threshold]);
Enter fullscreen mode Exit fullscreen mode

2. Efficient State Management

Instead of heavy Redux or complex context, we use minimal local state:

const [loadingStates, setLoadingStates] = useState<Record<number, boolean>>({});
const [errorStates, setErrorStates] = useState<Record<number, boolean>>({});
Enter fullscreen mode Exit fullscreen mode

3. Performance First

  • Memoization with useCallback and useMemo
  • Functional state updates to avoid stale closures
  • Clean observer setup and teardown
  • Lazy rendering: Only load what's necessary
const mediaElements = useMemo(
  () => items.map((item, index) => defaultRenderItem(item, index)),
  [items, defaultRenderItem]
);
Enter fullscreen mode Exit fullscreen mode

♿️ Accessibility Built-In

The component includes essential accessibility features:

  • ARIA roles (role="feed" and role="region")
  • Keyboard navigation (arrow keys)
  • Screen reader support with proper labels
  • Focus management with tabIndex
<div
  role="feed"
  aria-label="Vertical video feed"
  tabIndex={0}
  onKeyDown={handleKeyDown}
  // ... other props
>
Enter fullscreen mode Exit fullscreen mode

🧰 Development Setup

I kept the tooling modern and robust:

  • TypeScript for safer code
  • Rollup to bundle for both ESM and CommonJS
  • Jest + Testing Library for component testing
  • ESLint + Prettier for clean formatting
  • Husky for pre-commit hooks

Example Rollup config:

export default {
  input: 'src/index.ts',
  output: [
    { file: 'dist/index.js', format: 'cjs', sourcemap: true },
    { file: 'dist/index.esm.js', format: 'esm', sourcemap: true },
  ],
  // more config...
};
Enter fullscreen mode Exit fullscreen mode

📚 What I Learned

  • Simplify early — Complex setups kill performance.
  • TypeScript is a must — Caught so many edge cases during development.
  • Visibility and resource management are critical for smooth feeds.
  • Test-driven development is key — especially when dealing with IntersectionObserver behaviors.

📦 How to Use react-vertical-feed

Installation:

npm install react-vertical-feed
# or
yarn add react-vertical-feed
Enter fullscreen mode Exit fullscreen mode

Then use it like this:

import { VerticalFeed } from 'react-vertical-feed';

<VerticalFeed
  items={[
    { src: '/videos/video1.mp4', muted: true, controls: false },
    { src: '/videos/video2.mp4', muted: true, controls: false },
    // more items...
  ]}
/>
Enter fullscreen mode Exit fullscreen mode

You can find the full source on GitHub and a demo here.
Contributions and feedback are welcome!

Tiugo image

Modular, Fast, and Built for Developers

CKEditor 5 gives you full control over your editing experience. A modular architecture means you get high performance, fewer re-renders and a setup that scales with your needs.

Start now

Top comments (2)

Collapse
 
nevodavid profile image
Nevo David

Insane work - actually so much stuff goes into making it smooth like that. you think keeping it simple is always the best bet or do bigger projects need more complexity?

Collapse
 
reinaldosimoes profile image
Reinaldo Simoes • Edited

I think early on keeping it simple is always the best move. Simplicity = speed + less bugs. You can examine each feature as its own project once it grows in size.

Neon image

Next.js applications: Set up a Neon project in seconds

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Get started →

👋 Kindness is contagious

Value this insightful article and join the thriving DEV Community. Developers of every skill level are encouraged to contribute and expand our collective knowledge.

A simple “thank you” can uplift someone’s spirits. Leave your appreciation in the comments!

On DEV, exchanging expertise lightens our path and reinforces our bonds. Enjoyed the read? A quick note of thanks to the author means a lot.

Okay