# Build a Custom TikTok Autoplay React Hook With Intersection Observer

Have you ever wondered how social media applications like TikTok, Instagram, or Twitter detect a particular video post that is in the viewport, autoplay it, and then stop it immediately after it goes out of view?

In this article, I will explain how Intersection Observer can be used to implement this autoplay and pause feature by creating a React custom Hook for use in a TikTok clone.

As a developer, you might want to implement an autoplay feature in a video player application, [lazy load an image](https://blog.logrocket.com/lazy-loading-using-the-intersection-observer-api/), or detect when an advertisement is in the viewport of a user’s browser. With Intersection Observer you can do all these.

## **What is Intersection Observer?**

Intersection Observer is a JavaScript browser API that asynchronously monitors the position of DOM elements with respect to the client’s viewport or a root element.

### **How Intersection Observer works**

Basically, the Intersection Observer API triggers a callback function in specific situations.

These situations include when the position of the selected element comes into the client’s viewport, when a selected element intersects a parent or root element, or when the observer is initially declared.

### **Specifications and browser compatibility**

At the time of writing this article, the specifications are still a working draft*.* However, updates can be found [here](https://w3c.github.io/IntersectionObserver/).

As for the browser compatibility, here is the current report:

![](https://paper-attachments.dropboxusercontent.com/s_27F54750736534E986D2E60F6C3491B4AFF8491308890171E1134DE46933F221_1632991834237_broswer_compatibility.png align="left")

### **Applications for Intersection Observer**

Intersection Observer can be used for a wide variety of various applications outside of the scope of this article.

They include optional rendering of DOM elements, [l](https://blog.logrocket.com/lazy-loading-using-the-intersection-observer-api/)azy [l](https://blog.logrocket.com/lazy-loading-using-the-intersection-observer-api/)oading, loading content on-demand with [infinite scrolling](https://blog.logrocket.com/react-hooks-infinite-scroll-advanced-tutorial/), rendering advertisements and animations, and creating carousels.

The example I am using in this article (creating a custom autoplay Hook for a TikTok clone) can help you become familiar with the Intersection Observer API in order to start exploring the other options it can offer your apps.

## **Using Intersection Observer**

Firstly, we want to find out if our browser supports the Intersection Observer API.

We can write a condition to check, like so:

```javascript
if ('IntersectionObserver' in window) {
    console.log("IntersectionObserver is supported!");
} else {
    console.log("IntersectionObserver is not supported!");
}
```

The `ItersectionObserver` object is usually structured like this:

```javascript
let options= {
    root: null,
    rootMargin: '0px',
    threshold: 0.5
};

const callback = (entries){ // entries = array of targeted elements
    entries.forEach(entry=>{
        // what happens each entry
    })
}

let observerObj = new IntersectionObserver(callback, options);
observerObj.observe();
```

Here, the `IntersectionObserver` object accepts two arguments. The `callback` function, which is triggered after Intersection Observer is executed, and an optional `options` object. This is an object with certain properties that determine when, and how, the Intersection Observer works.

### **The** `callback` function

When the `callback` the function is executed, a list of targeted elements is checked by the Intersection Observer. These elements all have specific properties.

Examples of those properties are:

* `boundingClientRect`:
    
* `intersectionRatio`:
    
* `intersectionRect`
    
* `isIntersecting`
    
* `rootBounds`
    
* `target`
    
* `time`
    

These properties are used to check the current element’s behavior relative to their root element.

In this article, we will be using [`isIntersecting`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry/isIntersecting) to check if the current entry is intersecting with the root. This will be the indicator that our video is in the viewport, and therefore ready to begin playing.

### **The** `options` object

The `options` object contains the following properties:

The `root` is the browser’s viewport by default, or if set as `null`. If an element is specified as the root, it has to be a parent to the targeted element. The root is what the targeted element needs to intersect with before the `callback` function is triggered

The `rootMargin` sets the margin around the root element before detecting the intersection. By default, it is 0 (which triggers the action exactly when the `root` property enters the viewport), but it can be valued in the same manner as a [CSS margin](https://developer.mozilla.org/en-US/docs/Web/CSS/margin) in case you’d like the `callback` function to occur at a different moment.

The `threshold` represents what percentage of the targeted element should intersect `root` before the `callback` function is [executed.](http://executed.It) It can be either a number or an array of numbers; the accepted values range from 0 to 1.

If it is 0, it means the first pixel of the target element needs to intersect with the `root` element before the `callback` function is executed. if it is 0.5, 50 per cent of the target element needs to intersect with the `root`, and so on.

## **Targeting an element to be observed**

To target an element with JavaScript, we have to use the `querySelector` function, which will search the DOM for a given `id` or `class`.

In React, we can use the `useRef` Hook to target an element. Then, we pass `targetElement` as a parameter in the observe function, like so:

```javascript
/*
In JavaScript we can use querySelector to select a DOM element like this...
*/
let targetElement = document.querySelector('#item')
observerObj.observe(targetElement)

//In React we can use the useRef hook like this...
let targetRef = useRef(null); //Set a component to be ref of targetRef
let targetElement = targetRef.current
observerObj.observe(targetElement)
```

In the TikTok clone, we will be using the `useRef` Hook to target each video component in order to track when it comes into the viewport.

# **Creating an Intersection Observer custom Hook in React**

To create a reusable Intersection Observer Hook, we will create a new function called `useElementOnScreen` and implement Intersection Observer using `options` and `targetRef` we passed in as props:

```javascript
import { useEffect, useMemo, useState } from 'react'
const useElementOnScreen = (options, targetRef) => {
    const [isVisibile, setIsVisible] = useState()
    const callbackFunction = entries => {
        const [entry] = entries //const entry = entries[0]
        setIsVisible(entry.isIntersecting)
    }
    const optionsMemo = useMemo(() => {
        return options
    }, [options])
    useEffect(() => {
        const observer = new IntersectionObserver(callbackFunction, optionsMemo)
        const currentTarget = targetRef.current
        if (currentTarget) observer.observe(currentTarget)

        return () => {
        if(currentTarget) observer.unobserve(currentTarget)
        }
    }, [targetRef, optionsMemo])
    return isVisibile
}
export default useElementOnScreen 
```

Having understood what the `options` and `targetRef` are, we need to pass them as props to the `useElementOnScreen` Hook, as we will need them as parameters for a new Intersection Observer function.

Then, we set a default state for the element’s `visibility` as `null`.

Inside the `callback` function, we are setting the `isVisible` state to the value returned if the targeted Element `isIntersecting` (we are always expecting `true` or `false`).

After observing the target element, we return the `isVisible` state. The returned value of `isVisible` is what we will use to decide when a video should play or stop.

If the `isVisible` state of a video component is `true` we play the video, else if it is `false` we stop the video.

## **Using the Intersection Observer custom Hook in a TikTok clone**

### **Setting up the application**

For the sake of brevity, I have created a starter project that contains the entire source code of the TikTok clone where we will implement the Intersection Observer hook we just created above. It is available on my [GitHub repository](https://github.com/wolz-CODElife/Tiktok-clone).

To start the application running, open your terminal to a new work folder and run the following commands:

```bash
git clone https://github.com/wolz-CODElife/Tiktok-clone.git

cd Tiktok-clone

npm install
```

In the folder downloaded, the following files and directories should be present:

![](https://paper-attachments.dropboxusercontent.com/s_27F54750736534E986D2E60F6C3491B4AFF8491308890171E1134DE46933F221_1633234797556_files.png align="left")

The files and folders we are working with are inside the [`src`.As](http://src.As) shown above, I have already included the Intersection Observer hook that we created in the previous section of this article in the `hooks` directory. All that is remaining to do is to import the `useElementOnScreen` hooks in the TikTok application.

### **Setting up autoplay**

Now, let’s update the `Video.js` component to play and stop a video depending on its visibility status.

Inside the `Video.js` file, put the following code:

```javascript
import React, { useEffect, useRef, useState } from "react";
import "./Video.css";
import VideoFooter from "./VideoFooter";
import VideoSidebar from "./VideoSidebar";
import useElementOnScreen from './hooks/useElementOnScreen'
import VideoPlayButton from "./VideoPlayButton";
const Video = ({ url, channel, description, song, likes, messages, shares }) => {
  const [playing, setPlaying] = useState(false);
  const videoRef = useRef(null);
  const options = {
      root: null,
      rootMargin: '0px',
      threshold: 0.3
  }
  const isVisibile = useElementOnScreen(options, videoRef)
  const onVideoClick = () => {
    if (playing) {
      videoRef.current.pause();
      setPlaying(!playing);
    } else {
      videoRef.current.play();
      setPlaying(!playing);
    }
  };
  useEffect(() => {
    if (isVisibile) {
      if (!playing) {        
        videoRef.current.play();
        setPlaying(true)
      }
    }
    else {
      if (playing) {        
        videoRef.current.pause();
        setPlaying(false)
      }
    }
  }, [isVisibile])


  return (
    <div className="video">
      <video className="video_player" loop preload="true" ref={videoRef} onClick={onVideoClick} src={url}></video>
      <VideoFooter channel={channel} description={description} song={song} />
      <VideoSidebar likes={likes} messages={messages} shares={shares} />
      {!playing && <VideoPlayButton onVideoClick={onVideoClick} />}
    </div>
  );
};
export default Video;
```

Here, we imported the custom Hook (`useElementOnScreen`), then used the value returned (which could be `true` or `false`) as the `isVisible` value.

Note that we set the following options for the Intersection Observer: `root` is `null`, which means we are using the window as a parent element. `rootMargin` is `0px`, and `threshold` is `0.3` which means once 30 per cent of the target element is in the viewport, the callback function is triggered.

Next, we use `UseEffect` to change the `playing` state of the video if the `isVisible` value changes, like so:

```javascript
if (isVisibile) {
      if (!playing) {        
        videoRef.current.play();
        setPlaying(true)
      }
    }
    else {
      if (playing) {        
        videoRef.current.pause();
        setPlaying(false)
      }
    }
```

This code means that, if the video is visible, the `playing` state is set to `true`. If it is not yet playing, and if the video is not visible, the `playing` state is set to `false`.

With this done, we can run the application with the following:

```bash
npm start
```

If everything goes well, we should have something like this:

![](https://paper-attachments.dropboxusercontent.com/s_27F54750736534E986D2E60F6C3491B4AFF8491308890171E1134DE46933F221_1633236840410_GIF-2021-10-03-05-51-59.gif align="left")

If you wish to change the videos or even use a live database, edit the `video` state in `App.js`.

Currently, we have the following array of objects:

```json
[
    {
      url: 'https://res.cloudinary.com/codelife/video/upload/v1633232723/tiktok-clone/tiktok2_qxafx3.mp4',
      channel: 'DanceCrew',
      description: 'Video by Lara Jameson from Pexels',
      song: 'Bounce - Ruger',
      likes: 250,
      messages: 120,
      shares: 40
    },
    {
      url: 'https://res.cloudinary.com/codelife/video/upload/v1633232725/tiktok-clone/tiktok1_np37xq.mp4',
      channel: 'Happyfeet',
      description: '#happyfeetlegwork videos on TikTok',
      song: 'Kolo sound - Nathan',
      likes: 250,
      messages: 120,
      shares: 40
    },
    {
      url: 'https://res.cloudinary.com/codelife/video/upload/v1633232726/tiktok-clone/tiktok3_scmwvk.mp4',
      channel: 'thiskpee',
      description: 'The real big thug boys💛🦋 The real big thug boys💛🦋 ',
      song: 'original sound - KALEI KING 🦋',
      likes: 250,
      messages: 120,
      shares: 40
    },
  ]
```

# **Conclusion**

Having created the application successfully, we should have learned how Intersection Observer works and how you can use it to implement an autoplay feature similar to that in TikTok or Instagram.

With this knowledge, you can try implementing lazy loading images, carousels or even an infinitely scrolling blog feeds page!

You can check the live demo of my TikTok clone [here](https://wctiktok-4102d.web.app/).I advise viewing it on a desktop browser for the best experience.

If you have any questions or remarks, please feel free to let me know in the comments.
