A React video chat app connects browsers in real time using WebRTC, with React components handling UI and an RTC SDK managing rooms, media tracks, and signaling. VideoSDK's React SDK wraps this stack in hooks like useMeeting and useParticipant, letting teams ship group video calling in under a day instead of building STUN, TURN, and SFU infrastructure from scratch.

Most React developers who add video calling to a web app discover the gap between a working demo and a production call within the first week. Rendering a local webcam preview takes an afternoon. Handling token security, browser autoplay restrictions, participant reconnection, and group call scaling takes considerably longer.

This article explains what a React video chat app actually requires under the hood, walks through a complete VideoSDK implementation step by step, and covers the architecture and production decisions that tutorial-level posts skip.

This guide targets React SDK v1.1.0. If you are coming from a v0.x project, one change dominates everything below: every method returned by a VideoSDK hook is now asynchronous and throws on failure, where the v0.x versions were fire-and-forget. The migration guide walks through the full list. The code here is written the v1 way throughout, so you can follow it top to bottom on a fresh project.

How Does React Video Calling Work?

React video calling routes browser media through WebRTC while React state and context keep the UI synchronized with who is in the room and which tracks are active.

The Meeting Provider Layer

MeetingProvider is the React context wrapper that initializes a VideoSDK session with a meeting ID, authentication token, and default media settings (microphone on, camera off, display name). Every child component inside the provider tree can access meeting state through useMeeting without prop drilling.

The Participant Hook Layer

useParticipant(participantId) returns everything needed to render one person in the call: webcamStreammicStreamwebcamOnmicOnisLocal, and displayName. React refs attach those streams to <audio> and video elements so the browser plays incoming media.

The MediaStream API Bridge

Browsers do not render raw WebRTC tracks directly in JSX. Each track must be wrapped in a MediaStream object and assigned to a media element's srcObject property. The ParticipantView component in this guide uses useMemo for video streams and useEffect for audio streams to handle track changes when participants toggle their camera or microphone.

Group calls beyond two participants require a Selective Forwarding Unit (SFU) on the server side. Without an SFU, each browser must upload a separate video stream to every other participant, which exceeds typical home upload bandwidth at four or more people. VideoSDK routes group sessions through managed SFU infrastructure automatically.

VideoSDK is a platform that allows developers to create rich in-app experiences such as embedding real-time video, voice, real-time recording, live streaming, and real-time messaging. VideoSDK is available in JavaScriptReactjsReact-NativeiOSAndroid and Flutter to be seamlessly integrated. Video SDK also provides a pre-built SDK, which enables you to integrate real-time communication with your application in just 10 minutes.

Prerequisites

Before proceeding, ensure that your development environment meets the following requirements:

  • VideoSDK Developer Account (Not having one? Follow VideoSDK Dashboard)
  • Basic understanding of React
  • React Video SDK
  • Have Node and NPM installed on your device
  • The basic understanding of Hooks (useState, useRef, useEffect)
  • React Context API (optional)
One should have a Video SDK account to generate tokens. Visit Video SDK Dashboard to generate a token

Getting Started with the Code!?

Follow the steps to create the environment necessary to add video chat to your app.

Create a new React app

Create a new React App using the below command

$ npm create vite@latest videosdk-rtc-react-app -- --template react
$ cd videosdk-rtc-react-app
$ npm install

Vite is used here instead of create-react-app. CRA is no longer maintained and npx create-react-app fails on current Node versions, so it can no longer be step one of anything.

Install Video SDK

Install the Video SDK using the below-mentioned npm command. Make sure you are in your react app directory before you run this command.

$ npm install "@videosdk.live/react-sdk"

Version 1.1.0 or later exports a VideoPlayer component that renders a participant's video for you, so no separate player library is required. If you prefer the manual MediaStream approach shown later in this guide, install react-player as well and pin the 2.x line, because react-player 3.x renamed the url prop to src and removed playinglightpip and controls:

//Only for the legacy ReactPlayer rendering path
$ npm install "react-player@^2.16.0"

Structure of the project

Your project structure should look like this after creating the app with Vite

root
   ├── node_modules
   ├── public
   ├── index.html
   ├── src
   │    ├── API.js
   │    ├── App.jsx
   │    ├── App.css
   │    ├── main.jsx
   .    .

We are going to use functional components to leverage React's reusable component architecture. There will be components for users, videos and controls (mic, camera, leave) over the video.

App Architecture

The app will contain a MeetingView component which includes ParticipantView which will render the participant's name, video, audio, etc. We will also have a Controls component that will allow users to perform operations like leave and toggle media.

Video SDK Image

We are going to work on two files:

  • API.js: Responsible for handling API calls such as generating unique meetingId and token
  • App.js: Responsible to render MeetingView and join the meeting.

To build a React video chat app, use these 5 steps

STEP 1: Get started with API.js

Before moving on, we must create an API request to generate a unique meetingId. You will need an authentication token, which you can create either through the videosdk-rtc-api-server-examples or directly from the VideoSDK Dashboard for developers.

//Auth token we will use to generate a meeting and connect to it
export const authToken = "<Generated-from-dashbaord>";
// API call to create meeting
export const createMeeting = async ({ token }) => {
  const res = await fetch(`https://api.videosdk.live/v2/rooms`, {
    method: "POST",
    headers: {
      authorization: `${authToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({}),
  });
  //Destructuring the roomId from the response
  const { roomId } = await res.json();
  return roomId;
};

STEP 2: Wireframe App.js with all the components

To build up a wireframe of App.js, we are going to use Video SDK Hooks and Context Providers. VideoSDK provides MeetingProvider, MeetingConsumer, useMeeting, and use participant hooks. Let's understand each of them.

First, we will explore Context Provider and Consumer. Context is primarily used when some data needs to be accessible by many components at different nesting levels.

  • MeetingProvider: It is a Context Provider. It accepts value config and token as props. The Provider component accepts a value prop to be passed to consuming components that are descendants of this Provider. One Provider can be connected to many consumers. Providers can be nested to override values deeper within the tree.
  • MeetingConsumer: It is Context Consumer. All consumers that are descendants of a Provider will re-render whenever the Provider’s value prop changes.
  • useMeeting: It is meeting react hook API for a meeting. It includes all the information related to the meeting such as participants, streams etc.
  • useParticipant: It is participant hook API. useParticipant hook is responsible for handling all the events and props related to one particular participant such as join, leave, mute, etc.

Meeting Context helps to listen to all the changes when a participant joins a meeting or changes Mic or Camera etc.

Let's get started with changing a couple of lines of code in App.js.

import "./App.css";
import React, { useEffect, useRef, useState } from "react";
import {
  MeetingProvider,
  MeetingConsumer,
  useMeeting,
  useParticipant,
} from "@videosdk.live/react-sdk";
import { authToken, createMeeting } from "./API";

function JoinScreen() {
  return null;
}

function VideoComponent(props) {
  return null;
}

function Controls(props) {
  return null;
}

function Container(props) {
  return null;
}

function App() {
  const [meetingId, setMeetingId] = useState(null);

  const getMeetingAndToken = async (id) => {
    const meetingId =
      id == null ? await createMeeting({ token: authToken }) : id;
    setMeetingId(meetingId);
  };

  return authToken && meetingId ? (
    <MeetingProvider
      config={{
        meetingId,
        micEnabled: true,
        webcamEnabled: false,
        name: "C.V. Raman",
      }}
      token={authToken}
    >
      <MeetingConsumer>
        {() => <Container meetingId={meetingId} />}
      </MeetingConsumer>
    </MeetingProvider>
  ) : (
    <JoinScreen getMeetingAndToken={getMeetingAndToken} />
  );
}

export default App;

Step 3: Implement Join Screen

The join screen will work as a medium to either schedule a new meeting or to join an existing meeting.

Output:

Video SDK Image

Step 4: Implement Container and Controls

The next step is to create MeetingView and Controls components to manage features such as join, leave, mute, and unmute.

join() returns a promise in v1 and rejects if the token is invalid, the room does not exist, or the network drops mid-handshake. Await it inside an async handler and reset the JOINING state in the failure path, otherwise a failed join leaves the user staring at "Joining the meeting..." forever with an unhandled rejection in the console.

function MeetingView(props) {
  const [joined, setJoined] = useState(null);
  //Get the method which will be used to join the meeting.
  //We will also get the participants list to display all participants
  const { join, participants } = useMeeting({
    //callback for when meeting is joined successfully
    onMeetingJoined: () => {
      setJoined("JOINED");
    },
    //callback for when meeting is left
    onMeetingLeft: () => {
      props.onMeetingLeave();
    },
    //centralized error handling - individual calls still need their own try/catch
    onError: (error) => {
      console.error("VideoSDK error", error.code, error.message);
    },
  });

  const joinMeeting = async () => {
    setJoined("JOINING");
    try {
      await join();
    } catch (error) {
      console.error("join failed", error);
      setJoined(null);
    }
  };

  return (
    <div className="container">
      <h3>Meeting Id: {props.meetingId}</h3>
      {joined && joined == "JOINED" ? (
        <div>
          <Controls />
          {/* For rendering all the participants in the meeting */}
          {[...participants.keys()].map((participantId) => (
            <ParticipantView
              participantId={participantId}
              key={participantId}
            />
          ))}
        </div>
      ) : joined && joined == "JOINING" ? (
        <p>Joining the meeting...</p>
      ) : (
        <button onClick={joinMeeting}>Join</button>
      )}
    </div>
  );
}

Apart from that Control Component will be required to handle user actions.

Every method on useMeeting is asynchronous in v1, so none of them can be passed straight to onClick as a bare reference any more. On top of that, the media methods reject an overlapping call with ERROR_OPERATION_IN_PROGRESS, code 3055, so a double-click on the mic button throws. A single busy flag covers both: it awaits the call inside try/catch and disables the controls until it settles.

function Controls() {
  const { leave, toggleMic, toggleWebcam } = useMeeting();
  const [busy, setBusy] = useState(false);

  const run = async (label, fn) => {
    if (busy) return;
    setBusy(true);
    try {
      await fn();
    } catch (error) {
      console.error(`${label} failed`, error.code, error.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div>
      <button onClick={() => run("leave", leave)}>Leave</button>
      <button disabled={busy} onClick={() => run("toggleMic", toggleMic)}>
        toggleMic
      </button>
      <button disabled={busy} onClick={() => run("toggleWebcam", toggleWebcam)}>
        toggleWebcam
      </button>
    </div>
  );
}

Note that Controls only mounts once joined === "JOINED", which matters more in v1 than it did before: every hook method throws ERROR_ACTION_PERFORMED_BEFORE_MEETING_JOINED if it runs before onMeetingJoined fires.

(a) Output of Container Component:

Video SDK Image

(b) Output of Controls Component:

Video SDK Image

STEP 5: Implement Participant View

Before implementing the video component, We need to understand a couple of concepts.

(a) Forwarding Ref for mic and camera

Ref forwarding is a technique for automatically passing a ref through a component to one of its children. We are going to use Refs to attach audio and video tracks with components.

const webcamRef = useRef(null);
const micRef = useRef(null);

(b) useParticipant Hook​

useParticipant hook is responsible for handling all the properties and events of one particular participant who joined in the meeting. It will take participantId as an argument.

const { webcamStream, micStream, webcamOn, micOn } = useParticipant(
  props.participantId
);

(c) MediaStream API

MediaStream is useful to add MediaTrack to the audio/video tag to play the audio or video.

const webcamRef = useRef(null);
const mediaStream = new MediaStream();
mediaStream.addTrack(webcamStream.track);

webcamRef.current.srcObject = mediaStream;
webcamRef.current
  .play()
  .catch((error) => console.error("videoElem.current.play() failed", error));

(d) Rendering with VideoPlayer and AudioPlayer

The SDK ships the two components that do the wiring in (c) for you, and the render media guide now recommends them over a third-party player. VideoPlayer attaches the track, handles the autoplay promise, and mirrors the local participant's own tile without being asked. AudioPlayer does the same for micStream. Mounting AudioPlayer for the local participant would play your own microphone back at you, so it is skipped when isLocal is true.

function ParticipantView(props) {
  const { webcamOn, micOn, isLocal, displayName } = useParticipant(
    props.participantId
  );

  return (
    <div>
      <p>
        Participant: {displayName} | Webcam: {webcamOn ? "ON" : "OFF"} | Mic:{" "}
        {micOn ? "ON" : "OFF"}
      </p>

      {/* Never play your own mic back to yourself */}
      {!isLocal && (
        <AudioPlayer participantId={props.participantId} type="audio" />
      )}

      {webcamOn ? (
        <VideoPlayer
          participantId={props.participantId}
          type="video"
          containerStyle={{ height: "300px", width: "300px" }}
          videoStyle={{ height: "100%", width: "100%", objectFit: "contain" }}
        />
      ) : (
        <div style={{ height: "300px", width: "300px", background: "#C0C2C9" }}>
          webcam off
        </div>
      )}
    </div>
  );
}

(e) The manual rendering path with ReactPlayer

If you would rather attach the tracks yourself, this is the same component built on the MediaStream bridge from (c). It needs react-player@^2.16.0 - the 3.x line renamed url to src and dropped playinglightpip and controls, so these props silently render nothing on a fresh npm install react-player.

import ReactPlayer from "react-player"; // v2.x

function ParticipantView(props) {
  const micRef = useRef(null);
  const { webcamStream, micStream, webcamOn, micOn, isLocal, displayName } =
    useParticipant(props.participantId);

  const videoStream = useMemo(() => {
    if (webcamOn && webcamStream) {
      const mediaStream = new MediaStream();
      mediaStream.addTrack(webcamStream.track);
      return mediaStream;
    }
  }, [webcamStream, webcamOn]);

  useEffect(() => {
    if (micRef.current) {
      if (micOn && micStream) {
        const mediaStream = new MediaStream();
        mediaStream.addTrack(micStream.track);

        micRef.current.srcObject = mediaStream;
        micRef.current
          .play()
          .catch((error) =>
            console.error("videoElem.current.play() failed", error)
          );
      } else {
        micRef.current.srcObject = null;
      }
    }
  }, [micStream, micOn]);

  return (
    <div>
      <p>
        Participant: {displayName} | Webcam: {webcamOn ? "ON" : "OFF"} | Mic:{" "}
        {micOn ? "ON" : "OFF"}
      </p>
      <audio ref={micRef} autoPlay playsInline muted={isLocal} />
      {webcamOn && (
        <ReactPlayer
          //
          playsinline // very very imp prop
          pip={false}
          light={false}
          controls={false}
          muted={true}
          playing={true}
          //
          url={videoStream}
          //
          height={"300px"}
          width={"300px"}
          onError={(err) => {
            console.log(err, "participant video error");
          }}
        />
      )}
    </div>
  );
}
Video SDK Image

We are done with the implementation of a customized video calling app in ReactJS using VideoSDK. To explore more features go through Basic and Advanced features.

Schedule a Demo with Our Live Video Expert!

Discover how VideoSDK can help you build a cutting-edge real-time video app.

Conclusion

We have successfully completed the video calling app using ReactJS. If you wish to add functionalities like chat messaging, screen sharing, polls, etc, you can always check out our documentation. If you face any difficulty with the implementation, you can check out the example on GitHub or connect with us on our Discord community.

More React Resources

Frequently Asked Questions

How do I build a video chat app in React?

Build a video chat app in React by installing @videosdk.live/react-sdk, creating a backend or dashboard token, wrapping your app in MeetingProvider, and using useMeeting to join rooms and useParticipant to render each person's audio and video streams. The five-step process in this guide covers project setup through a working multi-participant call. For production, generate short-lived tokens from a server endpoint rather than hardcoding credentials in api.js.

What is VideoSDK React SDK?

VideoSDK React SDK is the @videosdk.live/react-sdk npm package that provides React hooks and context providers for integrating VideoSDK's real-time audio and video infrastructure into React web applications. It exposes MeetingProvideruseMeetinguseParticipant, and usePubSub APIs that abstract WebRTC signaling, TURN relay, and SFU-based group call routing. Teams use it to add video calling, screen sharing, in-call chat, and recording to React apps without building WebRTC infrastructure from scratch.

Can I use WebRTC directly in React without an SDK?

Yes, you can use WebRTC directly in React by calling navigator.mediaDevices.getUserMedia() for local media and RTCPeerConnection for peer connections, but you must also build a signaling server, provision STUN and TURN servers, and implement ICE restart and group call SFU logic yourself. Direct WebRTC in React gives full control over codecs and bitrate but typically requires two to six weeks of engineering before reaching production reliability. VideoSDK's React SDK wraps the same WebRTC foundation with managed infrastructure for teams that prioritize speed to market.

Does React video calling work in all browsers?

React video calling works in all major modern browsers that support WebRTC, including Chrome, Firefox, Safari, and Edge. Safari on iOS requires the playsInline attribute on media elements to prevent fullscreen takeover during calls. Browser autoplay policies block unmuted audio until the user interacts with the page, so production apps handle play() promise rejections and mute local audio elements to prevent echo. Teams should test on both Chromium and WebKit engines before launch because track rendering behavior differs slightly between them.

How many participants can a React video chat app support?

A React video chat app using VideoSDK's SFU infrastructure supports group calls with dozens of participants, limited primarily by each user's download bandwidth and the UI layout's ability to render multiple tiles. Peer-to-peer WebRTC without an SFU degrades beyond three to four participants because each browser must upload a separate video stream to every other participant. For large webinars or audiences above fifty participants, consider VideoSDK's interactive live streaming mode instead of a full participant video grid.

Is VideoSDK free for React video chat development?

VideoSDK offers a free developer tier that includes API access and credits for testing React video chat integrations. New accounts receive $20 free credit and no credit card is required. Paid usage is billed per minute of video once free credits are consumed. Verify current pricing on the VideoSDK pricing page before estimating production costs for your expected call volume.

What features can I add beyond basic video calling in React?

Beyond basic video calling, VideoSDK's React SDK supports screen sharing through enableScreenShare(), in-call text chat through usePubSub("CHAT"), polls, collaborative whiteboard, cloud recording via backend API calls, and real-time transcription. Each feature integrates through the same MeetingProvider context without additional WebSocket infrastructure. Consult the VideoSDK React documentation for feature-specific implementation guides.