Build a TikTok Clone with React and Firebase

Build a TikTok Clone with React and Firebase


In this article, we will build our TikTok clone with React and firebase, and also we will explore the cool feature of React hooks.

TikTok video-sharing app has become very popular among entertainment applications these days. This application has millions of users who span across the world.

Learn about React Animation in easy guide

Pre-requisite

  • Basic understanding of JavaScript ES6
  • Basic understanding of HTML and CSS
  • Have Nodejs installed on your machine
  • Prior understanding of react.js

Technologies

  • React
  • Firebase
  • Material UI (icons)

Project overview

Our TikTok clone will be able to display a video the also users will be able to play, pause, like the video, view the title of the song playing, and the number of likes, comments, and shares. Here is a live demo of our clone https://tik-tok-clone.netlify.app/

TikTok clone

Let’s get started as we create a new project using the create-react-app so go to a directory where you will store this project and type the following in the terminal

create-react-app tik-tok-clone

The above command uses the create-react-app CLI tool to generate a react boilerplate project for us so that we don’t have to configure any tooling.

For the command above to work, the create-react-app CLI tool must be installed globally using the command below.

npm install -g create-react-app

Spin up the server with the following:

npm start

Components

Our tik tok clone will consist of the following components:

  • Video-component
  • VideoSidebar component
  • VideoFooter component

We will delete some files that are not necessary for this project. These files include (App.css, App.test.js, logo.svg and registerServiceWorker.js)

Next, make the following change to the index.js file

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(<App/>, document.getElementById('root'));

In the src folder, create a folder called components with the following folders video, VideoFooter and iconBar.

Lets build the video component.
The video component is the parent of the VideoFooter and IconBar component.
In the video folder create the Video.js file with the following:

 import React, { useRef, useState } from 'react';
import './Video.css';
import VideoFooter from "../videoFooter/VideoFooter"
import VideoSidebar from '../videoSidebar/VideoSidebar';
function Video() {
    return (
        <div className="video">
            <video className="video__player" src="https://v16m.tiktokcdn.com/00f8150467034acf33c0036f54dc624a/5f497764/video/tos/useast2a/tos-useast2a-pve-0068/5c92cd711b4c4d11a0f7560389ff3514/?a=1233&br=2200&bt=1100&cr=0&cs=0&dr=0&ds=3&er=&l=2020082815291201019018913720166B55&lr=tiktok_m&mime_type=video_mp4&qs=0&rc=Mzg8NjY0a2RodTMzPDczM0ApaTY8MzdnOWVlN2k4ZzVmOWdwcWdqLW5mNWNfLS0xMTZzc2AzNS0vMS41MDYwMWBhM2A6Yw%3D%3D&vl=&vr="></video>
          {/*<VideoFooter />
          <VideoSidebar />*/}
        </div>
    )
}
export default Video

N/B: we comment the VideoFooter and VideoSidebar component to avoid errors since they are not available yet.
For the video url get to tiktok and inspect a video of your choice and copy the url.
In the video folder create the Video.css file with the following:

  .video {
    border: 1px solid white;
    background-color: white;
    /* snap begins */
    width: 100%;
    height: 100%;
    position: relative;
    scroll-snap-align: start;
    /* snap ends */
}
.video__player {
    width: 100%;
    height: 100%;
    object-fit: fill;
}

Next, we will add the play and pause functionality to our video component.

  const [playing, setPlaying] = useState(false);
    const videoRef = useRef(null);
    const onVideoPress = () => {
        if(playing) {
            videoRef.current.pause();
            setPlaying(false)
        } else {
            videoRef.current.play();
            setPlaying(true)
        }
    }

We hannesed the react useState hook for creating state and the useRef hook to create a reference to the html video element since our components are functional.

<video className="video__player" src="https://v16m.tiktokcdn.com/00f8150467034acf33c0036f54dc624a/5f497764/video/tos/useast2a/tos-useast2a-pve-0068/5c92cd711b4c4d11a0f7560389ff3514/?a=1233&br=2200&bt=1100&cr=0&cs=0&dr=0&ds=3&er=&l=2020082815291201019018913720166B55&lr=tiktok_m&mime_type=video_mp4&qs=0&rc=Mzg8NjY0a2RodTMzPDczM0ApaTY8MzdnOWVlN2k4ZzVmOWdwcWdqLW5mNWNfLS0xMTZzc2AzNS0vMS41MDYwMWBhM2A6Yw%3D%3D&vl=&vr=" ref={videoRef} onClick={onVideoPress}></video> 

Now, the video component should be as follows:

import React, { useRef, useState } from 'react';
import './Video.css';
import VideoFooter from "../videoFooter/VideoFooter"
import VideoSidebar from '../videoSidebar/VideoSidebar';
function Video() {
    const [playing, setPlaying] = useState(false);
    const videoRef = useRef(null);
    const onVideoPress = () => {
        if(playing) {
            videoRef.current.pause();
            setPlaying(false)
        } else {
            videoRef.current.play();
            setPlaying(true)
        }
    }
    return (
        <div className="video">
            <video className="video__player" src="https://v16m.tiktokcdn.com/00f8150467034acf33c0036f54dc624a/5f497764/video/tos/useast2a/tos-useast2a-pve-0068/5c92cd711b4c4d11a0f7560389ff3514/?a=1233&br=2200&bt=1100&cr=0&cs=0&dr=0&ds=3&er=&l=2020082815291201019018913720166B55&lr=tiktok_m&mime_type=video_mp4&qs=0&rc=Mzg8NjY0a2RodTMzPDczM0ApaTY8MzdnOWVlN2k4ZzVmOWdwcWdqLW5mNWNfLS0xMTZzc2AzNS0vMS41MDYwMWBhM2A6Yw%3D%3D&vl=&vr=" ref={videoRef} onClick={onVideoPress}></video> 
          {/*<VideoFooter />
          <VideoSidebar />*/}
        </div>
    )
}
export default Video

To play and pause the video, we have to import the video component in the app component.

Update app.js as follows

import Reactfrom 'react';
import './App.css';
import Video from './components/video/Video';

function App() {
  return (
      <div className="app">
        <div className="app__videos">
          <Video/>
        </div>
      </div>
  );
}
export default App;

Now we can play, and pause our video.

VideoSidebar component

Before we create this component we will install material icon and material ui so that we can use the material ui icons.

npm install @material-ui/core
npm install @material-ui/icons

In the videoSidebar folder create the VideoSidebar.js file with the following:

import React, { useState} from 'react'
import  FavoriteIcon from "@material-ui/icons/Favorite"
import MessageIcon from "@material-ui/icons/Message"
import FavoriteBorderIcon from "@material-ui/icons/FavoriteBorder"
import ShareIcon from "@material-ui/icons/Share"
import "./IconBar.css"
function VideoSidebar() {
    return (
        <div className="videoSidebar">
            <div className="videoSidebar__button">
                <FavoriteIcon fontSize="large" />
                <p>300</p>
            </div>
            <div className="videoSidebar__button">
                <MessageIcon fontSize="large"/>
                <p>300</p>
            </div>
            <div className="videoSidebar__button">
                <ShareIcon fontSize="large"/>
                <p>300</p>
            </div>
        </div>
    )
}
export default VideoSidebar

In the videoSidebar folder create the VideoSidebar.css file with the following:

.videoSidebar{
    position: absolute;
    top: 50%;
    right: 10px;
    color: white;
}
.videoSidebar__button {
    padding-bottom: 20px;
    text-align: center;
}

Next, we will implement the like and unlike feature.

const [liked, setLiked ] = useState(false);

In the snippet below we check if like is true then our next click should set it to false and vice versa.
When ever like is set to true, the number of likes increases.

 <div className="videoSidebar">
            <div className="videoSidebar__button">
                {liked ? 
                    (<FavoriteIcon fontSize="large" onClick={e => setLiked(false)}/>) : (<FavoriteBorderIcon fontSize="large" onClick={e => setLiked(true)}/>)

                }
                <p>{liked ? 300 + 1 : 300}</p>
            </div>
            <div className="videoSidebar__button">
                <MessageIcon fontSize="large"/>
                <p>300</p>
            </div>
            <div className="videoSidebar__button">
                <ShareIcon fontSize="large"/>
                <p>300</p>
            </div>
        </div>

Up until now, our app is yet to fetch data dynamically. In order to achieve this, we need
to do the following:

  1. set up firebase, to store all our data
  2. Connect our project to firebase

Setting up firebase

Firebase is provides developers with servers, APIs and datastore, all written so generically that developers can modify it to suit most needs. It is user friendly
In this article we will use the firestore to store our data.

Head to fiarebase.google.com and sign up. It is free for small projects.

Next you will be directed to your firebase projects page like the following:

Firebase console

Click on the Add project button.

  • step 1: Add the name of your project (tik-tok-clone)
  • Step 2: check to enable Google Analytics for this project and click continue
  • Step 3: Select the default account for firebase.

After the firebase project is successfully created, click on the web icon and follow the prompt to register your app.

Next Install Firebase CLI

 npm install -g firebase-tools

and continue to console.

Next, click on the web icon and select the config option as follow:

Connect our project to firebase

Install firebase in the project

npm install firebase

Create a firebase.js file in the src folder with the following:

import firebase from "firebase";

 const firebaseConfig = {
    // paste your firebase config object here
 }
const firebaseApp = firebase.initializeApp(firebaseConfig)
const db = firebaseApp.firestore();
const auth = firebase.auth();
export { auth, db };

Adding data to our firestore

click on the cloud fire store on the sidebar to create a database. select start in test mode.
Next click on the enable button.

After the firestore is successfully created, click on the start collection.
next, enter videos as the collection id. Click the auto id to generate a random id for the videos collection.

Your final values should be as the following:

tiktok clone
firebase values

The value of the url field should be a valid tic tok video link.

Click save.

Note: Each time we add a new video detail to our firestore, our tik tik clone will update in real time. isn’t that amazing?

Rendering data dynamically in the components

We will modify the app.js file as follows:

import React, {useState, useEffect} from 'react';
import './App.css';
import Video from './components/video/Video';
import { db } from './firebase';

function App() {
  const [videos, setVideos] = useState([])
  useEffect( () => {
    db.collection("videos").onSnapshot(snapshot => setVideos(snapshot.docs.map(doc => doc.data())))
  }, [])
  return (
      <div className="app">
        <div className="app__videos">
          {videos.map(
            ({messages, url, likes, shares, description, channel, song}, i) => {
              return <Video
                key={i}
                messages={messages}
                likes={likes}
                shares={shares}
                description={description}
                channel={channel}
                song={song}
                url={url}
              />
            }

            )
          }
        </div>
      </div>
  );
}
export default App;

The useEffect hook replaces all the life cycle methods in a class base component. For more information on the useEffect hook, visit the react official documentation https://reactjs.org/docs/hooks-effect.html

When our app component is mounted on the DOM, the useEffect hook fetches all the data from the firebase database, while the setVideos method in the useState hook updates the videos property of the app component state with the fetched data.

With the map method, we traversed the videos array and destructured messages, url, likes, shares, description, channel and song. Finally, we passed these values to the Video component as props.

Now we should update the Video component as follows:

 import React, { useRef, useState } from 'react';
import './Video.css';
import VideoFooter from "../videoFooter/VideoFooter"
import VideoSidebar from '../videoSidebar/VideoSidebar';
function Video({url, song, description, channel, likes, messages, shares}) {
    const [playing, setPlaying] = useState(false);
    const videoRef = useRef(null);
    const onVideoPress = () => {
        if(playing) {
            videoRef.current.pause();
            setPlaying(false)
        } else {
            videoRef.current.play();
            setPlaying(true)
        }
    }
    return (
        <div className="video">
            <video className="video__player" ref={videoRef} onClick={onVideoPress}  src={url}></video>

            <VideoFooter channel={channel} description={description} song={song}/>
            <VideoSidebar messages={messages} shares={shares} likes={likes}/>
        </div>
    )
}
export default Video

The video component recieves the props, and further pass it down as props to the VideoFooter and the VideoSiderbar components.

VideoSiderbar component should be updated as follows:

 import React, { useState} from 'react'
import  FavoriteIcon from "@material-ui/icons/Favorite"
import MessageIcon from "@material-ui/icons/Message"
import FavoriteBorderIcon from "@material-ui/icons/FavoriteBorder"
import ShareIcon from "@material-ui/icons/Share"
import "./VideoSidebar.css"
function VideoSidebar({messages, shares, likes}) {
    const [liked, setLiked ] = useState(false);
    return (
        <div className="videoSidebar">
            <div className="videoSidebar__button">
                {liked ? 
                    (<FavoriteIcon fontSize="large" onClick={e => setLiked(false)}/>) : (<FavoriteBorderIcon fontSize="large" onClick={e => setLiked(true)}/>)

                }
                <p>{liked ?`${likes} + 1`: `${likes}`}</p>
            </div>
            <div className="videoSidebar__button">
                <MessageIcon fontSize="large"/>
                <p>{messages}</p>
            </div>
            <div className="videoSidebar__button">
                <ShareIcon fontSize="large"/>
                <p>{shares}</p>
            </div>
        </div>
    )
}
export default VideoSidebar

Footer component

Before we create this component we will install react-ticker. ***React Ticker*** is a lightweight, performant React component, that moves text, images and videos infinitely like a newsticker.

npm install react-ticker

In the videoFooter folder create the VideoFooter.js file with the following:

import React from 'react';
import "./VideoFooter.css"
import MusicNoteIcon from '@material-ui/icons/MusicNote';
import Ticker from 'react-ticker'
function VideoFooter({channel, description, song}) {
    return (
        <div className="videoFooter">
            <div className="videoFooter_text">
                <h3>@{channel}</h3>
                <p>{description}</p>
                <div className="videoFooter_ticker">
                    <MusicNoteIcon className="videoFooter_icon"/>
                    <Ticker mode="smooth">
                        {({ index }) => (
                            <>
                                <p>{song}</p>
                            </>
                        )}
                    </Ticker>
                </div>
            </div>
            <img className="videoFooter_record" src="https://static.thenounproject.com/png/934821-200.png" alt=""/>
        </div>
    )
}
export default VideoFooter;

In the videoFooter folder create the VideoFooter.css file with the following:

.videoFooter {
    position: relative;
    bottom: 150px;
    margin-left: 40px;
    color: white;
    display: flex;
}
.videoFooter_text {
    flex: 1;
}
.videoFooter_text > h3 {
    padding-bottom: 15px;
}
.videoFooter_text > p {
    padding-bottom: 15px;
}
.videoFooter_icon {
    position: absolute;
}
.videoFooter_ticker > .ticker {
    height: fit-content;
    width: 70%;
    margin-left: 30px;
}
.videoFooter_record {
    animation: spinTheRecord infinite 5s linear;
    height: 40px;
    filter: invert(1);
    position: absolute;
    bottom: 0;
    right: 20px;
}
@keyframes spinTheRecord {
    from{
        transform: rotate(0deg);
    }
    to{
        transform: rotate(360deg);
    }
}

Finally, we have implemented the above listed features of our TikTok clone.

Conclusion

In the course of building the TikTok clone, we have been able to unfold the use cases for the react hooks. You can always improve on the concepts by adding more features to this project. You can find the complete project for this article on Github. Happy coding.


Share on social media

//