Building Real-Time Apps with WebSockets and React
Build real-time web apps with WebSockets and React: live chat, notifications, collaborative features, and Socket.IO integration with step-by-step examples.
Get more content like this on Telegram!
Daily AI tips, notes & resources — free
Building Real-Time Apps with WebSockets and React
I built my first "real-time" feature using polling — an API call every 2 seconds to check for new messages. It worked, technically. It also hammered the server with thousands of unnecessary requests per hour.
WebSockets solved that problem and unlocked a category of features that polling simply can't provide elegantly: seeing someone else's cursor move, watching a document update as a teammate types, getting an instant notification when a payment goes through.
This tutorial builds a real-time chat application using WebSockets and React. By the end, you'll understand the full stack: native WebSockets, the React integration pattern, and Socket.IO for production-grade real-time.
Understanding WebSockets
How They Work
HTTP request:
Client → Request → Server → Response → Connection closes
WebSocket:
Client → Upgrade request → Server → Connection stays open
Client ←→ Server (messages can flow both directions, anytime)
The WebSocket handshake starts as an HTTP request with an Upgrade: websocket header. Once the server accepts, the connection upgrades to the WebSocket protocol and stays open.
Native WebSocket API
// Browser-side
const ws = new WebSocket('ws://localhost:3001');
ws.onopen = () => {
console.log('Connected');
ws.send(JSON.stringify({ type: 'greeting', text: 'Hello server!' }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
ws.onclose = () => console.log('Disconnected');
ws.onerror = (error) => console.error('Error:', error);
// Send a message
ws.send(JSON.stringify({ type: 'chat', text: 'Hello everyone!' }));
Part 1: Simple WebSocket Server
npm init -y
npm install ws
// server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3001 });
const clients = new Set();
wss.on('connection', (ws) => {
clients.add(ws);
console.log(`Client connected. Total: ${clients.size}`);
// Notify all clients about new connection
broadcast({ type: 'system', text: 'A user joined the chat' });
ws.on('message', (rawData) => {
try {
const message = JSON.parse(rawData);
console.log('Received:', message);
// Broadcast to all connected clients
broadcast(message);
} catch (err) {
console.error('Invalid message format:', err);
}
});
ws.on('close', () => {
clients.delete(ws);
broadcast({ type: 'system', text: 'A user left the chat' });
});
});
function broadcast(data) {
const message = JSON.stringify(data);
clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
}
console.log('WebSocket server running on ws://localhost:3001');
Part 2: React Chat Component
Custom Hook for WebSocket
// hooks/useWebSocket.ts
import { useState, useEffect, useRef, useCallback } from 'react';
interface Message {
id: number;
type: 'chat' | 'system';
text: string;
username?: string;
timestamp: string;
}
function useWebSocket(url: string, username: string) {
const [messages, setMessages] = useState<Message[]>([]);
const [connected, setConnected] = useState(false);
const wsRef = useRef<WebSocket | null>(null);
useEffect(() => {
const ws = new WebSocket(url);
wsRef.current = ws;
ws.onopen = () => {
setConnected(true);
// Announce ourselves
ws.send(JSON.stringify({
type: 'join',
username,
timestamp: new Date().toISOString(),
}));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
setMessages(prev => [...prev, { ...message, id: Date.now() }]);
};
ws.onclose = () => setConnected(false);
ws.onerror = () => setConnected(false);
return () => {
ws.close();
};
}, [url, username]);
const sendMessage = useCallback((text: string) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
wsRef.current.send(JSON.stringify({
type: 'chat',
text,
username,
timestamp: new Date().toISOString(),
}));
}
}, [username]);
return { messages, connected, sendMessage };
}
export default useWebSocket;
Chat UI Component
// components/Chat.tsx
import { useState, useRef, useEffect } from 'react';
import useWebSocket from '../hooks/useWebSocket';
function Chat({ username }: { username: string }) {
const [input, setInput] = useState('');
const messagesEndRef = useRef<HTMLDivElement>(null);
const { messages, connected, sendMessage } = useWebSocket('ws://localhost:3001', username);
// Auto-scroll to latest message
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim()) return;
sendMessage(input.trim());
setInput('');
};
return (
<div className="flex flex-col h-screen max-w-lg mx-auto">
{/* Header */}
<div className="p-4 bg-blue-600 text-white flex items-center justify-between">
<h1 className="font-bold">Chat Room</h1>
<span className={`text-xs px-2 py-1 rounded-full ${
connected ? 'bg-green-500' : 'bg-red-500'
}`}>
{connected ? 'Connected' : 'Disconnected'}
</span>
</div>
{/* Messages */}
<div className="flex-1 overflow-y-auto p-4 space-y-2">
{messages.map(msg => (
<div key={msg.id} className={`
${msg.type === 'system' ? 'text-center text-gray-400 text-sm' : ''}
${msg.type === 'chat' && msg.username === username ? 'text-right' : ''}
`}>
{msg.type === 'chat' && (
<div className={`inline-block max-w-[70%] rounded-2xl px-4 py-2 ${
msg.username === username
? 'bg-blue-600 text-white'
: 'bg-gray-100 text-gray-900'
}`}>
{msg.username !== username && (
<p className="text-xs text-gray-500 mb-1">{msg.username}</p>
)}
<p>{msg.text}</p>
</div>
)}
{msg.type === 'system' && <p>{msg.text}</p>}
</div>
))}
<div ref={messagesEndRef} />
</div>
{/* Input */}
<form onSubmit={handleSubmit} className="p-4 border-t flex gap-2">
<input
type="text"
value={input}
onChange={e => setInput(e.target.value)}
placeholder="Type a message..."
className="flex-1 border rounded-full px-4 py-2 focus:outline-none"
disabled={!connected}
/>
<button
type="submit"
disabled={!connected || !input.trim()}
className="bg-blue-600 text-white rounded-full px-6 py-2 disabled:opacity-50"
>
Send
</button>
</form>
</div>
);
}
Part 3: Production with Socket.IO
For production apps, Socket.IO adds automatic reconnection, rooms, and a better API:
npm install socket.io
npm install socket.io-client
Server with Socket.IO
// server.js
const { Server } = require('socket.io');
const httpServer = require('http').createServer();
const io = new Server(httpServer, {
cors: { origin: 'http://localhost:5173', methods: ['GET', 'POST'] }
});
io.on('connection', (socket) => {
console.log(`User connected: ${socket.id}`);
socket.on('join-room', (roomId, username) => {
socket.join(roomId);
socket.to(roomId).emit('user-joined', { username });
});
socket.on('chat-message', (roomId, message) => {
// Broadcast to everyone in room except sender
socket.to(roomId).emit('chat-message', message);
});
socket.on('disconnect', () => {
console.log(`User disconnected: ${socket.id}`);
});
});
httpServer.listen(3001);
React with Socket.IO Client
import { useEffect, useRef } from 'react';
import { io, Socket } from 'socket.io-client';
function useSocket(serverUrl: string) {
const socketRef = useRef<Socket | null>(null);
useEffect(() => {
socketRef.current = io(serverUrl, {
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000,
});
return () => {
socketRef.current?.disconnect();
};
}, [serverUrl]);
return socketRef;
}
WebSockets vs SSE vs Polling
| Polling | SSE | WebSocket | |
|---|---|---|---|
| Direction | Client → Server only | Server → Client only | Bidirectional |
| HTTP overhead | High | Low | Low |
| Reconnection | Manual | Automatic | Library-dependent |
| Use case | Simple, infrequent updates | Live feeds, notifications | Chat, collaboration |
| Complexity | Low | Low | Medium |
For the broader React patterns that complement real-time features, our React hooks tutorial covers the custom hooks pattern used here. For state management when WebSocket messages update global state, see our React state management 2025 guide. And for the Node.js backend serving WebSocket connections, our Node.js and Express REST API guide covers the server setup.
Frequently Asked Questions
What is a WebSocket vs HTTP?
HTTP is request-response (connection closes after each exchange). WebSocket is a persistent bidirectional connection — both sides can send messages anytime.
WebSockets or Server-Sent Events?
SSE for one-way server → client (notifications, feeds). WebSockets for bidirectional (chat, collaboration, games).
What is Socket.IO?
A library wrapping WebSockets with rooms, reconnection, and an event-based API. Better than raw WebSockets for Node.js server apps.
How do I use WebSockets in React?
A custom hook: create connection in useEffect, clean up (close) in the return function, store messages in state.
Frequently Asked Questions
AiTechWorlds Team
✓ Verified WriterThe AiTechWorlds team is passionate about AI, technology, and education. We create high-quality, research-backed content to help you learn, grow, and succeed in the modern digital world.
Related Articles
How to Deploy a React App to Vercel in 10 Minutes
Deploy a React app to Vercel in 10 minutes: from npm create vite to live URL, custom domain setup, environment variables, and preview deployments.
GraphQL vs REST: Which API Style Should You Learn in 2025?
GraphQL vs REST API compared honestly for 2025: when each makes sense, real code examples, and which API style to learn first as a developer.
JavaScript Promises and Async/Await: Finally Understand Them
JavaScript async await and Promises explained clearly: the event loop, Promise chains, async/await patterns, error handling, and common mistakes to avoid.
How to Pass a JavaScript Interview at Google, Meta, or Amazon
How to pass a JavaScript interview at top tech companies: closures, event loop, promises, DOM questions, system design, and real interview questions answered.