Using WebSockets in React: Complete Guide
WebSockets enable real-time, bidirectional communication between clients and servers. Here’s how to implement WebSockets effectively in React applications:
1. Basic WebSocket Connection
Creating a WebSocket Hook
import { useEffect, useRef, useState } from 'react';
function useWebSocket(url) {
const [messages, setMessages] = useState([]);
const [isConnected, setIsConnected] = useState(false);
const ws = useRef(null);
useEffect(() => {
ws.current = new WebSocket(url);
ws.current.onopen = () => {
setIsConnected(true);
console.log('WebSocket connected');
};
ws.current.onclose = () => {
setIsConnected(false);
console.log('WebSocket disconnected');
};
ws.current.onmessage = (event) => {
const message = JSON.parse(event.data);
setMessages(prev => [...prev, message]);
};
return () => {
if (ws.current) {
ws.current.close();
}
};
}, [url]);
const sendMessage = (message) => {
if (ws.current && isConnected) {
ws.current.send(JSON.stringify(message));
}
};
return { messages, sendMessage, isConnected };
}
// Usage
function ChatApp() {
const { messages, sendMessage, isConnected } = useWebSocket('wss://your-websocket-server.com');
const handleSend = () => {
sendMessage({ text: 'Hello', user: 'Me' });
};
return (
<div>
<div>Status: {isConnected ? 'Connected' : 'Disconnected'}</div>
<div>
{messages.map((msg, i) => (
<div key={i}>{msg.user}: {msg.text}</div>
))}
</div>
<button onClick={handleSend}>Send Message</button>
</div>
);
}
2. Reconnecting WebSocket
Enhanced Hook with Reconnection
function useReconnectingWebSocket(url, options = {}) {
const { reconnectInterval = 5000 } = options;
const [messages, setMessages] = useState([]);
const [isConnected, setIsConnected] = useState(false);
const ws = useRef(null);
const reconnectTimeout = useRef(null);
const connect = useCallback(() => {
ws.current = new WebSocket(url);
ws.current.onopen = () => {
clearTimeout(reconnectTimeout.current);
setIsConnected(true);
console.log('WebSocket connected');
};
ws.current.onclose = () => {
setIsConnected(false);
console.log('WebSocket disconnected - attempting reconnect');
reconnectTimeout.current = setTimeout(connect, reconnectInterval);
};
ws.current.onerror = (error) => {
console.error('WebSocket error:', error);
ws.current.close();
};
ws.current.onmessage = (event) => {
const message = JSON.parse(event.data);
setMessages(prev => [...prev, message]);
};
}, [url, reconnectInterval]);
useEffect(() => {
connect();
return () => {
clearTimeout(reconnectTimeout.current);
if (ws.current) {
ws.current.close();
}
};
}, [connect]);
const sendMessage = useCallback((message) => {
if (ws.current && ws.current.readyState === WebSocket.OPEN) {
ws.current.send(JSON.stringify(message));
}
}, []);
return { messages, sendMessage, isConnected };
}
3. WebSocket with Redux
WebSocket Middleware
const socketMiddleware = () => {
let socket = null;
return ({ dispatch }) => next => action => {
switch (action.type) {
case 'WS_CONNECT':
if (socket !== null) {
socket.close();
}
socket = new WebSocket(action.payload);
socket.onopen = () => {
dispatch({ type: 'WS_CONNECTED' });
};
socket.onclose = () => {
dispatch({ type: 'WS_DISCONNECTED' });
};
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
dispatch({ type: 'WS_MESSAGE', payload: data });
};
break;
case 'WS_DISCONNECT':
if (socket !== null) {
socket.close();
}
socket = null;
dispatch({ type: 'WS_DISCONNECTED' });
break;
case 'WS_SEND':
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(action.payload));
}
break;
default:
return next(action);
}
};
};
// Store setup
import { createStore, applyMiddleware } from 'redux';
const store = createStore(
rootReducer,
applyMiddleware(socketMiddleware())
);
// Usage
store.dispatch({ type: 'WS_CONNECT', payload: 'wss://your-server.com' });
store.dispatch({ type: 'WS_SEND', payload: { message: 'Hello' } });
4. WebSocket with Context API
WebSocket Context Provider
import React, { createContext, useContext, useEffect, useRef, useState } from 'react';
const WebSocketContext = createContext(null);
export function WebSocketProvider({ url, children }) {
const [isReady, setIsReady] = useState(false);
const [messages, setMessages] = useState([]);
const ws = useRef(null);
useEffect(() => {
ws.current = new WebSocket(url);
ws.current.onopen = () => {
setIsReady(true);
};
ws.current.onclose = () => {
setIsReady(false);
};
ws.current.onmessage = (event) => {
const message = JSON.parse(event.data);
setMessages(prev => [...prev, message]);
};
return () => {
ws.current.close();
};
}, [url]);
const sendMessage = useCallback((message) => {
if (isReady && ws.current) {
ws.current.send(JSON.stringify(message));
}
}, [isReady]);
const value = { isReady, messages, sendMessage };
return (
<WebSocketContext.Provider value={value}>
{children}
</WebSocketContext.Provider>
);
}
export function useWebSocketContext() {
return useContext(WebSocketContext);
}
// Usage in components
function ChatComponent() {
const { isReady, messages, sendMessage } = useWebSocketContext();
// ...component logic
}
5. WebSocket with Socket.IO
Socket.IO Client Setup
import { io } from 'socket.io-client';
function useSocketIO(url, options) {
const [isConnected, setIsConnected] = useState(false);
const [messages, setMessages] = useState([]);
const socketRef = useRef();
useEffect(() => {
socketRef.current = io(url, options);
socketRef.current.on('connect', () => {
setIsConnected(true);
});
socketRef.current.on('disconnect', () => {
setIsConnected(false);
});
socketRef.current.on('message', (message) => {
setMessages(prev => [...prev, message]);
});
return () => {
if (socketRef.current) {
socketRef.current.disconnect();
}
};
}, [url, options]);
const sendMessage = useCallback((event, message) => {
if (socketRef.current && isConnected) {
socketRef.current.emit(event, message);
}
}, [isConnected]);
return { isConnected, messages, sendMessage };
}
// Usage
function SocketIOComponent() {
const { isConnected, messages, sendMessage } = useSocketIO('http://your-server.com', {
autoConnect: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000,
});
const handleSend = () => {
sendMessage('chatMessage', { text: 'Hello', user: 'Me' });
};
// ...render logic
}
6. WebSocket Best Practices
- Connection Management:
- Implement automatic reconnection
- Handle connection errors gracefully
- Clean up connections on component unmount
- Message Handling:
- Use message IDs for tracking
- Implement acknowledgments for critical messages
- Consider message batching for high-frequency updates
- Security:
- Use
wss://
(WebSocket Secure) in production - Authenticate connections (e.g., with JWT)
- Validate all incoming messages
- Performance:
- Throttle rapid messages when needed
- Use binary data for large payloads
- Consider compression for text-heavy applications
- Testing:
- Mock WebSocket server for tests
- Test connection loss scenarios
- Verify message serialization/deserialization
7. Error Handling and Monitoring
Enhanced Error Handling
function useWebSocketWithErrors(url) {
const [error, setError] = useState(null);
// ...other state
useEffect(() => {
const ws = new WebSocket(url);
ws.onerror = (event) => {
setError('WebSocket error occurred');
console.error('WebSocket error:', event);
};
// ...other handlers
return () => {
ws.close();
};
}, [url]);
const clearError = () => setError(null);
return { error, clearError, /* ...other returns */ };
}
Connection Status Monitoring
function ConnectionStatus({ isConnected, lastActivity }) {
return (
<div style={{
color: isConnected ? 'green' : 'red',
padding: '10px',
border: `1px solid ${isConnected ? 'green' : 'red'}`
}}>
{isConnected ? (
<>
Connected
{lastActivity && (
<div>Last activity: {new Date(lastActivity).toLocaleTimeString()}</div>
)}
</>
) : (
'Disconnected'
)}
</div>
);
}
WebSockets are powerful for real-time features like chat, notifications, live updates, and collaborative editing. Choose the implementation approach that best fits your application’s architecture and requirements.