Zoom Clone App with React and 100ms SDK (part II)
Tags: react, webdev, beginners, javascript
Published: 2022-07-04 10:10:36

This article is a continuation of the How to build a Zoom Clone App with React tutorial.

[Part 1](https://dev.to/benjaminbusari/zoom-clone-with-react-and-100ms-sdk-part-i-3o7b) focuses on creating the Zoom layout. That is a required step before you can follow the tutorial in this article, so kindly check that first before continuing with this.

Zoom is a cloud-based video conferencing platform that can be used for video conferencing meetings, audio conferencing, webinars, meeting recordings, and live chat. You need a free account to start your own calls for up to 100 people; paid versions can support up to 1,000 people. You can make unlimited phone calls, hold unlimited meetings, and even record both.

## Overview of 100ms?

[100ms](https://www.100ms.live/) is a cloud-based platform that lets you integrate video and audio conferencing into your application. It provides [APIs and SDKs](https://www.100ms.live/docs/javascript/v2/foundation/basics) through which you can set up and manage telecommunication services on the client and server-side applications.

It provides a dashboard that makes it quite easy to manipulate descriptive features of a video/audio platform from in-app interaction to the session recording. The 100ms SDK and packages are easy-to-use tools that save time without compromising effectiveness.

## Prerequisites and What You Will Learn

As a prerequisite to understanding and following this tutorial, you should have basic knowledge of React.

In this article, you will learn how to build this clone using the following concepts:

- Creating a **new 100ms Video Conference Application**
- **Connect** your client React app to 100ms to get the **APP_ACCESS_KEY** and **APP_SECRET** from the [developer section](https://dashboard.100ms.live/login) in the dashboard.
- **Customizing** the SDK’s components by overwriting CSS classes and using Context from the SDK

## Important Terms to Know

Before diving into the tutorial, here are a few terms you should be familiar with:

- **Peer:** A peer is an object returned by 100ms SDKs that contains all information about a user - name, role, video track, etc.
- **Room:** A room is a basic object that 100ms SDKs return on a successful connection. This contains references to peers, tracks, and everything you need to render a live audio/video app.
- **Role:** A role defines who a peer can see/hear, the quality at which they publish their video, whether they have permission to publish video/screen share, mute someone, change someone's role, or more in a call.
- **Track**: A track represents either the audio or video that a peer is publishing.
- **PeerCount:** A peer count indicates the number of users participating in the room.

This tutorial will cover the following:

- Muting and unmuting the audio and video for both local and remote peers.
- Sharing screen for presentation for the host.
- Creating a modal to invite other users to the video conference

## Let’s Build Our Zoom Clone App

### Developers Setup

For this tutorial, you will install packages with NPM, so you need to have [Node installed](https://nodejs.org/en/) on your system. Alternatively, you can use [yarn](https://yarnpkg.com/) if you prefer that.

For this tutorial, I used [Node v16.15.0.](https://nodejs.org/dist/latest-v16.x/docs/api/) I encourage you to use the same version if you are coding along.

### Setting up your 100ms Application

100ms is a cloud platform that allows developers to add video and audio conferencing to Web, Android, and iOS applications.

The platform provides REST APIs, SDKs, and a dashboard that makes it simple to capture, distribute, record, and render live interactive audio, and video.

To build a live app on 100ms.live, you need a 100ms.live account. If you do not have one, head over to the [login](https://dashboard.100ms.live/login) to login to the dashboard. You can do this either by login in with your Google account, GitHub or your email.

To create a live app:

1. Go to your [100ms dashboard](https://dashboard.100ms.live/login)
2. Select either to continue with Google, Github, or your Email
3. Choose a Template **(Video Conferencing)**


![100ms Template](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fsxv2er8ldcxf0mzr3xd.png)

1. Select Account Type **(Personal)**


![Account Type](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pd74tv3ioa496ahle1rt.png)

1. Set your domain and region




![domain selection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/je23526r9ko14c1wtwb5.png)

1. Select option to either join as a host or as a guest (for this tutorial, select HOST)


![role selection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e362uoky41cy3ltxszqs.png)


In this tutorial, the guest role doesn't have the privilege to share their screens, you can give yours the opportunity to share screen while setting your roles.

Congratulations your App is Live 👊. Next is to access the developer's panel so we can save our token, access keys, roomID etc.

### Project Setup

- **Link 100ms SDK to the app:** Install the 100ms react SDK and project dependencies.

```jsx
## npm
npm install --save @100mslive/react-sdk@latest
## yarn
yarn add @100mslive/react-sdk@latest
```

### Retrieving Credentials

- **Retrieve Credentials:** Get **token_endpoint, room_id, APP_ACCESS_KEY,** and **APP_SECRET** from the developer section of the dashboard.

Once you’re done creating your custom application(in this case we created a video conference app), head on over to the **Developers** tab to get your token endpoint URL. This endpoint URL is very important as this is where we get our token from.

The next thing to do is for us to get the **ROOM ID ,** To obtain the room ID, head to the **Rooms** tab on the Dashboard. You’ll see an existing room. Copy the room ID, we will be using this soon.



![roomID](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/51up0yrpxcca55glmm2v.png)

Next, navigate to the developers tab and copy your endpointURL and room ID



![endpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qaxmfqfog5so089rdmkh.png)

You can also set roles (guest or host) function like screen share, video and audio mute


![roles function](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/32zfhrkf6mrdekj7hhkb.png)

- **Create Room:** create a video room.
- **Build Clone:** Use `hmsAction` and `hmsStore` hooks to start building our clone. Fill in **token_endpoint** and **room_id**.

At the end of this tutorial, your ZoomMeet clone Interface should look like this:


![landing page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b4jdwbmnmpousqpb8x1j.png)

### Building the clone App

To use the 100ms SDK that we installed previously, we need to be familiar with two hooks:

1. **hmsActions:** This will help us perform actions such as joining the room, muting our audio/video, and sending messages.
2. **hmsStore:** This contains the complete state of the room at any given time such as the participant details etc.

After installing the SDK, navigate to your index.js file and have it look like this:

```jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { HMSRoomProvider } from "@100mslive/react-sdk";

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <HMSRoomProvider>
    <App />
  </HMSRoomProvider>,
);
```

Next, create a file and name it **fetchToken.js** 

Now, let's set some variables in our fetchtoken.js file. the code below will fetch your token from the saved endpoint URL returning a response, this is what we then further use in our code to set our Room for the conference.

```jsx
const endPoint = "https://prod-in2.100ms.live/hmsapi/zoomclone.app.100ms.live/";
//your saved endpoint URL from your dashboard

const fetchToken = async (user_id) => {
  const response = await fetch(`${endPoint}api/token`, {
    method: "POST",
    body: JSON.stringify({
      user_id,  // user_id different from the generated id in your developers tab
      role: "host", //host, guest
      room_id: "6288c1d9b873787aa26f06f0" //your room id
    })
  });
  const { token } = await response.json();
  return token;
};
export default fetchToken;
```

- Fill in your **token_endpoint** and **room_id**, as is shown above.
- To call the join function, add the following code to your `JoinRoom`function in the JoinForm**.js** file.  This is necessary because when a user joins a room, we want to display the room the user joined.
- You can get the UI layout and stylings here.

```jsx
import React, { useState } from 'react';
import './style.css';
import NavBar from '../UserInterface/navBar';

import { useHMSActions} from '@100mslive/react-sdk';
import fetchToken from "../fetchToken";

const JoinRoom = () => {
    const hmsActions = useHMSActions();
    const [userName, setUserName] = useState("");

    const handleSubmit = async (userName) => {
        const token = await fetchToken(userName);
        hmsActions.join({ 
        userName,
        authToken: token
        });
    };
    return(
        <>
            <NavBar />
            <hr></hr>
            <div id="content_container" style={{minHeight: 872}}  className="zoom-newcontent ">
                <div id="content" className="main-content">
                    <div className="mini-layout" id="join-conf">
                        <div className="mini-layout-body">
                            <h1 style={{fontSize: 25}}>Join Meeting</h1>
                            <div className="box">
                                <form id="join-form" className="form-vertical" onSubmit={(e) => {
                                        e.preventDefault();
                                        handleSubmit(userName);
                                    }}>
                                    <div className="form-group confno" style={{marginBottom: 30}}>
                                        <div className="controls">
                                            <label htmlFor="join-confno" style={{color: 747486, fontSize: 15,marginBottom: 10}}>Meeting ID or Personal Link Name</label>
                                            <input aria-describedby="rule-tip" id="join-confno" 
                                                type="text" 
                                                className="form-control input-lg confno" 
                                                autoComplete="off" maxLength="100" 
                                                placeholder="Enter Meeting ID or Personal Link Name" 
                                                value={userName}
                                                onChange={(e) => setUserName(e.target.value)}
                                                name="userName"
                                                required
                                                />
                                            <div id="errorContainer" className="wc-new-syle">
                                                <div id="join-errormsg" className="error hideme"><i></i><span></span></div>
                                            </div>
                                        </div>
                                    </div>
                                    <div className="form-group" style={{marginBottom: 16}}>
                                        <div className="controls">
                                            By clicking "Join", you agree to our <a href="https://zoom.us/terms">Terms of Services</a> and <a href="https://zoom.us/privacy">Privacy Statement</a>
                                        </div>
                                    </div>
                                    <div className="form-group" style={{marginBottom: 72}}>
                                        <div className="controls wc-new-syle">
                                            <button id="btnSubmit" role="button" style={{ width: 200, padding: 5}} className="btn btn-primary user submit">Join</button>
                                        </div>
                                    </div>
                                    <div className="form-group">
                                        <div className="controls wc-new-syle">
                                            <a id="btnRoomSystemJoin" className="doc" href="https://zoom.us/meeting/rooms">Join a meeting from an H.323/SIP room system</a>
                                        </div>
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </>
        
    )
}
export default JoinRoom;
```

From the above, when the button **join** is clicked, the **handlesubmit** function is triggered which fetches the token to authenticate the user trying to join the room.


![Join Room](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7knlntdr7gaink6pvvpt.png)

Next, we create a file and name it **VideoTile.js**, the function of the **VideoTile.js** is to **display** the video.

In the Video.js, your code should look like this:

```jsx
import React from "react";
import {
  useHMSActions,
  useHMSStore,
  selectCameraStreamByPeerID
} from "@100mslive/react-sdk";

const VideoTile = ({ peer, isLocal }) => {
  const hmsActions = useHMSActions();
  const videoRef = React.useRef(null);
  const videoTrack = useHMSStore(selectCameraStreamByPeerID(peer.id));

  React.useEffect(() => {
    (async () => {
      console.log(videoRef.current);
      console.log(videoTrack);
      if (videoRef.current && videoTrack) {
        if (videoTrack.enabled) {
          await hmsActions.attachVideo(videoTrack.id, videoRef.current);
        } else {
          await hmsActions.detachVideo(videoTrack.id, videoRef.current);
        }
      }
    })();
  }, [hmsActions, videoTrack]);

  return (
    <div className="flex m-auto">
      <div className="relative">
        <video
          ref={videoRef}
          autoPlay={true}
          playsInline
          muted={true}
          className={`object-cover h-64 w-screen h-screen rounded-lg shadow-lg ${
            isLocal ? "mirror" : ""
          }`}
        ></video>
        <div className="top-0 w-full absolute flex justify-center">
          <div className="px-2 py-1 text-sm bg-gray-600 text-white mt-2 ml-2 rounded-lg">{`${peer.name}`}</div>
        </div>
      </div>
    </div>
  );
};

export default VideoTile;
```

Next, create another file and name it **Peer.Js.**

```jsx
import { useVideo } from "@100mslive/react-sdk";

function Peer({ peer }) {
  const { videoRef } = useVideo({
    trackId: peer.videoTrack
  });
  return (
    <div className="peer-container">
      <video
        ref={videoRef}
        className={`peer-video ${peer.isLocal ? "local" : ""}`}
        autoPlay
        muted
        playsInline
      />
      <div className="peer-name">
        {peer.name} {peer.isLocal ? "(You)" : ""}
      </div>
    </div>
  );
}

export default Peer;
```

The Peer.js file sets 

Next, navigate to the **ControlBar.js**, this will allow us to control our room, adding toggle bars, etc to the room. Having designed the ControlBar.js in the previous tutorial, now we proceed to adding functionalities to each clickable buttons.

```jsx
import React, {useState} from "react";
import {
  useHMSActions,
  useHMSStore,
  selectIsLocalAudioEnabled,
  selectIsLocalVideoEnabled,
  selectIsLocalScreenShared,
  selectPeerCount
} from "@100mslive/react-sdk";

const ControlBar = () => {
  const hmsActions = useHMSActions();
  const isLocalAudioEnabled = useHMSStore(selectIsLocalAudioEnabled);
  const isLocalVideoEnabled = useHMSStore(selectIsLocalVideoEnabled);
  const isLocalScreenShared = useHMSStore(selectIsLocalScreenShared);
  const countParticipants = useHMSStore(selectPeerCount);

  const toggleAudio = async () => {
    await hmsActions.setLocalAudioEnabled(!isLocalAudioEnabled);
  };
  const toggleVideo = async () => {
    await hmsActions.setLocalVideoEnabled(!isLocalVideoEnabled);
  };
  const toggleScreen = async () => {
    await hmsActions.setScreenShareEnabled(!isLocalScreenShared);
  }
  const participants =  () => {
    hmsActions.setParticipantsCount(!countParticipants);
  }
  const invite = () => {
    document.getElementById("invite").classList.toggle("show");
  }
  const [show, setShow] = useState(false);
  
  return (
      
    <div class="main__controls">
        <div className="main__controls__block">
            <div className="main__controls__button main__mute_button" onClick={toggleAudio}>
                {isLocalAudioEnabled ? (
                  <>
                    <i className="fas fa-microphone"></i>
                    <span>Mute</span>
                  </>
                  ) : (
                  <>
                    <i className="fas fa-microphone-slash"></i>
                    <span>UnMute</span>
                  </>
                  )}
            </div>
            <div onClick={toggleVideo} className="main__controls__button main__video_button" >
                {isLocalVideoEnabled ? (
                  <>
                    <i className="fas fa-video"></i>
                    <span>Stop Video</span>
                  </>
                ) : (
                  <>
                    <i className="fas fa-video"></i>
                    <span>Play Video</span>
                  </>
                )}
            </div>
            <div className="main__controls__button">
                <i className="fas fa-shield-alt"></i>
                <span>Security</span>
            </div>
            <div className="main__controls__button part" onClick={invite}>
              <i className="fas fa-user-friends"></i>
              <span className="partLink">Participants <span className="participants">{countParticipants}</span></span>
                <div id="invite" className="dropdown-content">
                  <button onClick={() => setShow(true)}>Invite  
                    <span className="share-icon">
                      
                    </span>
                  </button>
                </div>
                <Modal onClose={() => setShow(false)} show={show} />
            </div>
            <div className="main__controls__button">
                <i className="fas fa-comment-alt"></i>
                <span>Chat</span>
            </div>
            <div onClick={toggleScreen} className="main__controls__button main__video_button" >
                <i className="fas fa-desktop"></i>
                {isLocalScreenShared ? "Unshare" : "Share Screen"}
            </div>
            <div className="main__controls__button">
                <i className="fas fa-record-vinyl"></i>
                <span>Record </span>
            </div>
            <div className="main__controls__button">
                <i className="fas fa-laugh"></i>
                <span>Reactions</span>
            </div>
            <div className="main__controls__button">
                <i className="fas fa-retweet"></i>
                <span>Apps</span>
            </div>
            <div className="main__controls__button">
                <i className="fas fa-clipboard"></i>
                <span>Whiteboard</span>
            </div>
        </div>
        <div className="main__controls__block">
          <div onClick={() => { hmsActions.endRoom(false, "reason") && hmsActions.leave();
            }} 
            className="main__controls__button"
          >
            <span className="leave_meeting">Leave Meeting</span>
          </div>
        </div>
    </div>
  );
};

export default ControlBar;
```

With the **ControlBar**, we can know how many participants we have in the room, we can share the screen during a presentation by clicking on Share Screen, and you can also invite others into the room by clicking on the **Participants** button and then clicking on **Invite.**



![room](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jgh75sxkxqtkbqptfl5k.png)

Now, let us create a modal to display when we click on the **invite.** The modal will show the room name and meeting passcode which also allows the user to copy the invite link and share.

Create a folder and name it **Modal**, then inside the folder create a file and name it **Modal.js** file, your modal.js file should look like this:

```jsx
import React from 'react';
import './modal.css';

const Modal = props => {

    if(!props.show) {
        return null
    }
    const fetchToken = {
        role: "host", //host, guest
        room_id: "6288c1d9b873787aa26f06f0",
        room_name: "FRONTEND"
      };
    
    return(
        <div className="modal">
            <div className="modal-content">
                <div className="modal-header">
                    <button onClick={props.onClose} className="button">Close</button>
                    <h5 className="modal-title">Invite People to join meeting {fetchToken.room_name}</h5>
                </div>
                <div className="modal-body">
                    <input className="modal-input" placeholder="Choose from the list or type to filter" />
                </div>
                <div className="modal-footer">
                    <button className="button">Copy invite link</button>
                    <div className="btn-right">
                        <h5>Meeting Passcode: {fetchToken.room_id} </h5>
                        <button className="button">Invite</button>
                    </div>
                </div>
            </div>
        </div>
    )
}
export default Modal;
```

Now, let's style the modal.js file. create a file and name it modal.css.

```css
.modal {
    background-color: #1C1E20;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 500px;
    width: 50%;
    margin-top: 40px;
    margin-left: 200px;
}
.modal-content {
    width: 600px;
    height: 410px;
    background-color: #1C1E20;
}
.modal-header {
    display: flex;
    gap: 1em;
    text-align: center;
}
.button {
    padding: 5px;
    background-color: #1C1E20;
    border: 0;
}
.modal-input {
    width: 100%;
    padding: 5px;
    padding: 2px;
}
.modal-header, .modal-footer {
    padding: 10px;
}
.modal-title {
    margin: auto;
}
.modal-body {
    padding: 10px;  
}
.modal-footer {
    margin-top: 250px;
    display: flex;
}
.btn-right {
    display: flex;
    justify-content: space-between;
    margin: auto;
    gap: 3em;
}
```


![modal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vegij6kahvsot61rzrri.png)

Now, we navigate to the **Room.js** file, our `localPeer` will be known as class. Your Room.js file should be modified to look like this:

```jsx
import React from "react";
import VideoTile from "./VideoTile";
import './view.css';
import {
  useHMSStore,
  selectLocalPeer,
  selectPeers
} from "@100mslive/react-sdk";
import ControlBar from "./Control/ControlBar";

const Room = () => {
 
  const localPeer = useHMSStore(selectLocalPeer);
  const peers = useHMSStore(selectPeers);
  
  return (
    <div class="main"> 
      <div class="main__left">
        <div class="main__videos">
          <div id="video-grid">
            <div className="flex flex-col mt-20">
              <div className="flex bg-gray-900 w-screen min-h-screen p-2 flex-wrap">
                {localPeer && <VideoTile peer={localPeer} isLocal={true} />}
                {peers &&
                  peers
                    .filter((peer) => !peer.isLocal)
                    .map((peer) => {
                      return (
                        <>
                          <VideoTile isLocal={false} peer={peer} />
                        </>
                      );
                    })}
              </div>
            </div> 
          </div>
        </div>
        <ControlBar />
      </div>
      <div className="main__right">
          <div className="main__header">
              <h6>Chat</h6>
          </div>
          <div className="main__chat_window">
              <ul className="messages">
              
              </ul>
          </div>
          <div className="main__message_container">
              <input id="chat_message" type="text" placeholder="Type message here..." />
          </div>
      </div>
    </div>
  );
};

export default Room;
```

Finally, we can modify our App.js file to look like this:

```jsx
import './components/styling/style.css'
import {
  useHMSActions,
  useHMSStore,
  selectIsConnectedToRoom
} from "@100mslive/react-sdk";
import Room from "./components/Room";
import JoinRoom from './components/LandingPage/JoinForm';
import fetchToken from "./components/fetchToken";

const App = () => {
    const hmsActions = useHMSActions();
    const isConnected = useHMSStore(selectIsConnectedToRoom);
    const handleSubmit = async (userName) => {
    const token = await fetchToken(userName);
    hmsActions.join({ authToken: token, userName });
  };

  return (
    <>{isConnected ? <Room /> : <JoinRoom handleSubmit={handleSubmit} />}</>
  );
};

export default App;
```

Your application should look like this:


![final](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k2doh0pr2dqyuqyzqbuk.png)

## Conclusion

In this tutorial, you have successfully created a zoom clone, and added functionalities to the share button, participants, audio, and invite. What the Zoom App lacks now is reactions (emojis and stickers), chat, and how to copy, share links and add another user to the video conference. I'll walk you through implementing this in Part 3.

[100ms](https://www.100ms.live/) is a cloud-based platform that lets you integrate video and audio conferencing into your application. It provides [APIs and SDKs](https://www.100ms.live/docs/javascript/v2/foundation/basics) through which you can set up and manage telecommunication services on the client and server-side applications.

The increasingly virtual, remote-first world, needs an infrastructure provider that allows these experiences to happen seamlessly. 100ms' best-in-class quality and ease of use are fuelling this revolution. 100ms provides multiple solutions matching both common and unique use cases, with just a few clicks and hooks. 💥.

You can also check out the code on GitHub [here](https://github.com/benjamin3327/ZoomClone.git)

You might also be interested in other guides using React and 100ms SDK:

[Building a Google Classroom Clone with React and 100ms SDK](https://www.100ms.live/blog/google-classroom-clone-react-100ms#why-100ms%3F)

[Building a Twitch Clone with React](https://www.100ms.live/blog/twitch-clone-in-react)

[Building a Discord Stage Channel](https://www.100ms.live/blog/build-discord-stage-channel-clone-hms?utm_source=cta&utm_medium=inline&utm_campaign=twtcl&utm_id=blogCTA)