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
- Basic WebRTC flow
- Project setup in React
- Media capture
- Peer connection setup
- Signaling basics
- Cleanup and common issues
Basic WebRTC flow
At a high level, WebRTC works like this:
- Capture media (camera/microphone)
- Create a peer connection
- Exchange SDP offers/answers via signaling
- Exchange ICE candidates
- 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!