ReactJS video call

Setting up a video call functionality can be quite complex if we don't know where to start or what the right libraries are. In this Answer, we'll be looking at how to set up a basic video call quickly in ReactJS!

Technologies used

  1. We'll be using ReactJS to make the user interface for our video call page.

  2. An Express server will be required to allow different clients to connect.

  3. Two libraries, Socket.io and Simple Peer, will be used to aid in making the calls.

Different technologies and libraries
Different technologies and libraries

Socket.io

Socket.io is a library that helps web applications communicate with servers in real-time. One great thing it provides us is bidirectional communication which means that the client and the server can send messages to each other at any time.

How does it work?

It uses WebSockets, which provides a persistent connection between the client and the server. This allows us to achieve real-time data exchange.

Simple Peer

Simple Peer is a library to establish direct connections i.e. peer to peer between web browsers.

How does it work?

It uses WebRTC that enables real-time audio and video streaming directly between browsers without intermediate servers. This helps us to set our code up quickly without having to dive deeper into the complexities.

Setting up a React project

Prior to using these libraries in a React project, we'll have to set a project up first. This can be done by following the steps given here.

Alternatively, these two commands can be run to set up the project quickly.

npx create-react-app react-proj
cd react-proj

Styling with Tailwind

For adding styles to our code, we will be making use of Tailwind CSS, a highly customizable library that provides class names to add different styles.

You can learn how to set it up here.

Installing the packages

We'll be needing the following packages in our code. Copy the command and paste it into your terminal to set up your project properly.

npm i express http cors socket.io simple-peer socket.io-client

Code sample

Now that we're done with the basic installations, let's create our own video call code! We will create a really basic page that allows copying a user ID, and any user connected to the same server can use it to call that user ID. We can do this to both send and receive calls.

Note: A local host server will only allow calls on the same machine. If you wish to take your video call functionality to a higher level, you can deploy the server online and simply change the API endpoint.

Our page should look something like this, with our video on one side and the person we wish to call on the other side.

Sample video call interface
Sample video call interface

Client code

import React, { useEffect, useRef, useState } from "react";
import Peer from "simple-peer";
import { io } from "socket.io-client";
// replace your server endpoint here
const socket = io("http://localhost:5000");
function VideoCall() {
const [me, setMe] = useState("");
const [stream, setStream] = useState();
const [receivingCall, setReceivingCall] = useState(false);
const [caller, setCaller] = useState("");
const [callerSignal, setCallerSignal] = useState();
const [callAccepted, setCallAccepted] = useState(false);
const [idToCall, setIdToCall] = useState("");
const [callEnded, setCallEnded] = useState(false);
const [name, setName] = useState("");
const userVideo = useRef();
const connectionRef = useRef();
const myVideo = useRef();
useEffect(() => {
navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then((stream) => {
setStream(stream);
if(myVideo)
myVideo.current.srcObject = stream;
});
socket.on("me", (id) => {
setMe(id);
});
socket.on("callUser", (data) => {
setReceivingCall(true);
setCaller(data.from);
setName(data.name);
setCallerSignal(data.signal);
});
}, [myVideo]);
const callUser = (id) => {
const peer = new Peer({
initiator: true,
trickle: false,
stream: stream,
});
peer.on("signal", (data) => {
socket.emit("callUser", {
userToCall: id,
signalData: data,
from: me,
name: name,
});
});
peer.on("stream", (stream) => {
userVideo.current.srcObject = stream;
});
socket.on("callAccepted", (signal) => {
setCallAccepted(true);
peer.signal(signal);
});
connectionRef.current = peer;
};
const answerCall = () => {
setCallAccepted(true);
const peer = new Peer({
initiator: false,
trickle: false,
stream: stream,
});
peer.on("signal", (data) => {
socket.emit("answerCall", { signal: data, to: caller });
});
peer.on("stream", (stream) => {
userVideo.current.srcObject = stream;
});
peer.signal(callerSignal);
connectionRef.current = peer;
};
const leaveCall = () => {
setCallEnded(true);
connectionRef.current.destroy();
};
return (
<>
<div>
<div className="flex flex-row h-full w-full justify-center gap-[15%] h-screen z-">
<div>
<div class="flex-grow flex flex-col items-center justify-center h-[90%]">
<span className="text-white font-bold text-3xl mb-4">Basic React JS video calling</span>
<span className="text-white font-bold text-md mb-4 text-center underline">
Copy your ID and anyone using the same server can use it to call you and vice versa!
</span>
<div class="flex flex-row gap-32">
<div class="flex flex-col items-center justify-center w-full">
<div className="video">
{stream && (
<video
className="rounded-full"
playsInline
muted
ref={myVideo}
autoPlay
style={{ width: "300px" }}
/>
)}
</div>
<span className="text-white font-bold text-lg mb-4">{caller}</span>
<p className="text-white">{me}</p>
</div>
<div class="flex flex-col items-center justify-center w-full">
{callAccepted && !callEnded ? (
<video
className="rounded-full"
playsInline
ref={userVideo}
autoPlay
style={{ width: "300px" }}
/>
) : (
<div className="flex flex-col justify-center items-center">
<img
src="https://w0.peakpx.com/wallpaper/416/423/HD-wallpaper-devil-boy-in-mask-red-hoodie-dark-background-4ef517.jpg"
class="rounded-full w-[15rem]"
/>
<span class="text-white font-bold text-lg">{idToCall}</span>
</div>
)}
</div>
</div>
<textarea
className="text-black"
defaultValue={idToCall}
onChange={(e) => {
setIdToCall(e.target.value);
}}
/>
<div>
{callAccepted && !callEnded ? (
<button className="text-black hover:text-gray-400 mr-6 font-bold bg-white rounded-md m-4 px-2" onClick={leaveCall}>
End Call
</button>
) : (
<button
className="text-black hover:text-gray-400 mr-6 font-bold bg-white rounded-md m-4 px-2"
onClick={() => callUser(idToCall)}
>
Call
</button>
)}
</div>
<div className="text-white">
{receivingCall && !callAccepted ? (
<div className="caller flex flex-col">
<h1 className="text-white">{caller} is calling...</h1>
<button
className="text-black text-xl hover:text-gray-400 mr-6 font-bold bg-white rounded-md m-4 px-2"
onClick={answerCall}
>
Answer
</button>
</div>
) : null}
</div>
</div>
</div>
</div>
</div>
</>
);
}
export default VideoCall;

Code explanation

  • Lines 1–3: We import the necessary React modules for our code, Peer from "simple-peer" for WebRTC communication, and io from "socket.io-client" for establishing a socket connection with the server.

  • Line 5: We establish a socket connection to the server running at "http://localhost:5000" (you can replace it with your own endpoint).

  • Lines 7–16: We define the VideoCall component. It initializes state variables for "me" (user ID), "stream" (media stream), "receivingCall" (whether a call is being received), "caller" (caller ID), "callerSignal" (signaling data from the caller), "callAccepted" (whether the call is accepted), "idToCall" (ID we want to call), "callEnded" (if the call has ended), and "name" (user name).

  • Lines 18–20: We create useRef instances to reference the two videos and the connection.

  • Lines 22–27: We use the useEffect hook to request access to the user's video and audio using navigator.mediaDevices.getUserMedia() and set the stream if successful.

  • Lines 29–31: We listen for the "me" event from the server, which sends a unique ID to us that anyone can use to call us.

  • Lines 33–39: We listen for the "callUser" event from the server, which means there's an incoming call. We set the receivingCall, caller, name, and callerSignal states accordingly.

  • Lines 41–55:We define a callUser method to initiate a call. It creates a new Peer instance, sets the options, and emits the "callUser" event to the server.

  • Lines 61–64: We listen for the "callAccepted" event from the server, which means that the call has been accepted. We set the callAccepted state and signal the peer to establish our connection.

  • Lines 69–88:We define the answerCall method to answer an incoming call. It sets the callAccepted state, creates a new Peer instance, emits the "answerCall" event to the server, and signals the peer to establish the connection.

  • Lines 89–91:We define the leaveCall method to end the call. Through this, we set the callEnded state and destroy the peer connection.

  • Lines 94–184: Finally, we write the JSX user interface code, which includes the video call interface, both video elements, an input to enter the ID to call, and buttons to call, answer, and end the call.

Server code

const express = require("express");
const http = require("http");
const cors = require("cors");
const app = express();
const server = http.createServer(app);
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const io = require("socket.io")(server, {
cors: {
origin: "*",
methods: ["GET", "POST"],
},
});
io.on("connection", (socket) => {
socket.emit("me", socket.id);
socket.on("disconnect", () => {
socket.broadcast.emit("callEnded");
});
socket.on("callUser", (data) => {
console.log(`Incoming call from ${data.from}`);
io.to(data.userToCall).emit("callUser", {
signal: data.signalData,
from: data.from,
name: data.name,
});
});
socket.on("answerCall", (data) => {
console.log(`Answering call from ${data.from}`);
io.to(data.to).emit("callAccepted", data.signal);
});
});
server.listen(5000, () => console.log("server is running on port 5000"));

Code explanation

  • Lines 1–3: We import the necessary modules for our code, including express for creating a web server, http for handling HTTP requests, and cors for allowing requests from different origins.

  • Lines 5–10: We set up an Express app, create an HTTP server using the app, and enable CORS and JSON middleware.

  • Lines 12–20: We set up a socket.io io instance on the server and include permission for other origins. We listen for a "connection" event, indicating a client has connected, and emit the "me" event to the connected client so that we can send its socket.id.

  • Lines 22–24: We handle the "disconnect" event when a client disconnects and then broadcast the "callEnded" event to the connected clients.

  • Lines 26–33: To initiate a call, we define the "callUser" event. We emit the "callUser" event to the target user data.userToCall, sending the signaling data, caller ID data.from, and the caller's name data.name, if any.

  • Lines 35–38: We handle the "answerCall" event when a client answers a call. We emit the "callAccepted" event to the calling client (specified by data.to), sending the signaling data (data.signal).

  • Line 41: We start the server on port 5000. Make sure to change the port according to your machine.

Complete code

Congratulations! We've succeeded in making the bare bones of a video-calling application.

The following code is the complete working code for the application. You can change the server endpoints according to your own machine.

module.exports = {
  content: ["./src/**/*.{html,js,jsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
};

Demonstration of a video call

Let's test our newly created video calling capabilities by using two browser windows side by side.

Test your knowledge!

Q

How do we take permission from the user for the camera and microphone?

A)

Using a Peer instance

B)

Using the getUserMedia function

C)

Using the stream object

Free Resources

Copyright ©2025 Educative, Inc. All rights reserved