React application showing a peer-to-peer WebRTC video call.

Setting Up WebRTC in React

WebRTC allows browsers to communicate directly with each other for real-time audio, video, and data sharing. This article walks through a practical React-based setup focused on clarity and real-world usage ra

What problem WebRTC solves

Traditional real-time communication relied heavily on centralized servers. WebRTC enables peer-to-peer communication, reducing latency and server cost. However, it still requires signaling servers and sometimes TURN servers to work reliably across networks.

In a React application, the main challenge is managing WebRTC’s lifecycle cleanly without fighting React’s rendering model.

What this guide covers

  1. Basic WebRTC flow
  2. Project setup in React
  3. Media capture
  4. Peer connection setup
  5. Signaling basics
  6. Cleanup and common issues

Basic WebRTC flow

At a high level, WebRTC works like this:

  1. Capture media (camera/microphone)
  2. Create a peer connection
  3. Exchange SDP offers/answers via signaling
  4. Exchange ICE candidates
  5. Stream media directly between peers

React only handles UI and lifecycle; WebRTC handles media and networking.


React project setup

Any modern React setup works (Vite, CRA, Next.js client components). The key requirement is access to browser APIs.

npm install

No additional WebRTC libraries are required.


Capturing media

Use getUserMedia to access the camera and microphone:

const stream = await navigator.mediaDevices.getUserMedia({
  video: true,
  audio: true
});
videoRef.current.srcObject = stream;

Always request permissions as a user action (button click) to avoid browser blocks.


Creating the peer connection

Create and store the peer connection using useRef to avoid re-creation on re-renders:

const pcRef = useRef(null);

pcRef.current = new RTCPeerConnection({
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' },
    {
      urls: 'turn:turn.example.com:3478',
      username: 'user',
      credential: 'pass'
    }
  ]
});

Using useRef ensures the connection persists across renders.


Adding media tracks

Attach tracks from the local stream:

stream.getTracks().forEach(track => {
  pcRef.current.addTrack(track, stream);
});

This step must happen before creating an offer.


Signaling (minimal)

WebRTC does not define signaling. You can use WebSocket, Socket.IO, or HTTP polling.

Example idea:

  • Client A creates offer
  • Sends offer via signaling server
  • Client B sets remote description and responds with answer
await pc.setRemoteDescription(offer);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);

ICE candidates should also be exchanged through the signaling channel.


Receiving remote stream

Listen for incoming tracks:

pcRef.current.ontrack = event => {
  remoteVideoRef.current.srcObject = event.streams[0];
};

Handling ICE candidates

Send candidates to the remote peer:

pcRef.current.onicecandidate = event => {
  if (event.candidate) {
    sendCandidateToServer(event.candidate);
  }
};

Without this step, peers may connect locally but fail across networks.


Cleanup on unmount

Always close connections when the component unmounts:

return () => {
  pcRef.current?.close();
};

This prevents camera locks and memory leaks.


Common mistakes

  • Creating peer connections inside render
  • Forgetting cleanup
  • Missing TURN server for production
  • Trying to skip signaling

Testing and debugging

  • Use chrome://webrtc-internals
  • Test across different networks
  • Simulate restrictive NAT using mobile hotspots

Final thoughts

WebRTC works best when React handles UI and lifecycle, and WebRTC logic lives in refs and effects. Keep signaling simple, always include TURN for production, and clean up resources properly.

This is an excerpt from Abdulvahab Shaikh's Setting Up WebRTC in React (A Practical Guide) article. I highly recommend you give it a read!

Related Articles