WebSockets
WebSockets
Full-duplex communication between client and server.
WebSocket Hook
// hooks/useWebSocket.js
function useWebSocket(url, options = {}) {
const [isConnected, setIsConnected] = useState(false);
const [lastMessage, setLastMessage] = useState(null);
const wsRef = useRef(null);
const reconnectTimeoutRef = useRef(null);
const { onMessage, onOpen, onClose, onError, reconnect = true } = options;
const connect = useCallback(() => {
const ws = new WebSocket(url);
wsRef.current = ws;
ws.onopen = () => {
setIsConnected(true);
onOpen?.();
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
setLastMessage(data);
onMessage?.(data);
};
ws.onclose = () => {
setIsConnected(false);
onClose?.();
if (reconnect) {
reconnectTimeoutRef.current = setTimeout(connect, 3000);
}
};
ws.onerror = (error) => {
onError?.(error);
};
}, [url, onMessage, onOpen, onClose, onError, reconnect]);
useEffect(() => {
connect();
return () => {
wsRef.current?.close();
clearTimeout(reconnectTimeoutRef.current);
};
}, [connect]);
const sendMessage = useCallback((data) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
wsRef.current.send(JSON.stringify(data));
}
}, []);
return { isConnected, lastMessage, sendMessage };
}
Chat Application
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const { isConnected, sendMessage } = useWebSocket(
`ws://localhost:8080/chat/${roomId}`,
{
onMessage: (data) => {
if (data.type === 'message') {
setMessages(prev => [...prev, data.payload]);
}
},
}
);
const handleSend = (e) => {
e.preventDefault();
if (input.trim()) {
sendMessage({
type: 'message',
payload: {
text: input,
timestamp: Date.now(),
},
});
setInput('');
}
};
return (
<div className="chat-room">
<div className="status">
{isConnected ? '🟢 Connected' : '🔴 Disconnected'}
</div>
<div className="messages">
{messages.map((msg, i) => (
<div key={i} className="message">
<strong>{msg.user}:</strong> {msg.text}
</div>
))}
</div>
<form onSubmit={handleSend}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type a message..."
/>
<button type="submit">Send</button>
</form>
</div>
);
}
Reconnection Logic
// Exponential backoff reconnection
function useReconnectingWebSocket(url, options = {}) {
const [reconnectAttempts, setReconnectAttempts] = useState(0);
const maxReconnectAttempts = options.maxReconnectAttempts || 10;
const getReconnectDelay = () => {
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
return delay + Math.random() * 1000; // Add jitter
};
const { isConnected, sendMessage } = useWebSocket(url, {
...options,
onClose: () => {
if (reconnectAttempts < maxReconnectAttempts) {
setTimeout(() => {
setReconnectAttempts(prev => prev + 1);
}, getReconnectDelay());
}
options.onClose?.();
},
onOpen: () => {
setReconnectAttempts(0);
options.onOpen?.();
},
});
return { isConnected, sendMessage, reconnectAttempts };
}
Server-Sent Events
Server-Sent Events
One-way server-to-client streaming.
SSE Hook
// hooks/useSSE.js
function useSSE(url, options = {}) {
const [data, setData] = useState(null);
const [isConnected, setIsConnected] = useState(false);
const [error, setError] = useState(null);
const eventSourceRef = useRef(null);
useEffect(() => {
const eventSource = new EventSource(url);
eventSourceRef.current = eventSource;
eventSource.onopen = () => {
setIsConnected(true);
setError(null);
};
eventSource.onmessage = (event) => {
const parsed = JSON.parse(event.data);
setData(parsed);
options.onMessage?.(parsed);
};
eventSource.addEventListener('update', (event) => {
const parsed = JSON.parse(event.data);
options.onUpdate?.(parsed);
});
eventSource.onerror = (event) => {
setError('Connection lost');
setIsConnected(false);
};
return () => {
eventSource.close();
};
}, [url]);
return { data, isConnected, error };
}
Real-time Notifications
function NotificationSystem() {
const [notifications, setNotifications] = useState([]);
useSSE('/api/notifications/stream', {
onMessage: (notification) => {
setNotifications(prev => [notification, ...prev]);
// Show browser notification
if (Notification.permission === 'granted') {
new Notification(notification.title, {
body: notification.message,
});
}
},
});
return (
<div className="notifications">
{notifications.map((notif, i) => (
<Notification key={i} notification={notif} />
))}
</div>
);
}
Live Data Feed
function LivePriceFeed({ symbol }) {
const [price, setPrice] = useState(null);
const [history, setHistory] = useState([]);
useSSE(`/api/prices/${symbol}/stream`, {
onMessage: (data) => {
setPrice(data.price);
setHistory(prev => [...prev.slice(-50), data]);
},
});
return (
<div className="price-feed">
<h3>{symbol}</h3>
<div className="current-price">
${price?.toFixed(2) || '--'}
</div>
<PriceChart data={history} />
</div>
);
}
SSE vs WebSockets
| Feature | SSE | WebSockets |
|---|---|---|
| Direction | Server → Client | Bidirectional |
| Protocol | HTTP | WS/WSS |
| Auto-reconnect | Built-in | Manual |
| Browser support | Modern browsers | Universal |
| Use case | Notifications, feeds | Chat, gaming |
Polling Strategies
Polling Strategies
Periodic data fetching when WebSockets/SSE aren't available.
Short Polling
// Simple interval polling
function usePolling(fetchFn, interval = 5000) {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const result = await fetchFn();
setData(result);
} catch (error) {
console.error('Polling error:', error);
} finally {
setIsLoading(false);
}
};
fetchData();
const intervalId = setInterval(fetchData, interval);
return () => clearInterval(intervalId);
}, [fetchFn, interval]);
return { data, isLoading };
}
// Usage
function OrderStatus({ orderId }) {
const { data: order } = usePolling(
() => fetchOrder(orderId),
3000 // Poll every 3 seconds
);
return (
<div>
<h3>Order #{orderId}</h3>
<p>Status: {order?.status || 'Loading...'}</p>
</div>
);
}
Smart Polling
// Adaptive polling with backoff
function useSmartPolling(fetchFn, options = {}) {
const {
initialInterval = 5000,
maxInterval = 60000,
backoffMultiplier = 1.5,
} = options;
const [interval, setInterval] = useState(initialInterval);
const [data, setData] = useState(null);
const [hasNewData, setHasNewData] = useState(false);
useEffect(() => {
let currentInterval = interval;
let timeoutId;
const poll = async () => {
try {
const result = await fetchFn();
if (JSON.stringify(result) !== JSON.stringify(data)) {
setData(result);
setHasNewData(true);
currentInterval = initialInterval; // Reset on new data
} else {
currentInterval = Math.min(
currentInterval * backoffMultiplier,
maxInterval
);
}
} catch (error) {
currentInterval = Math.min(
currentInterval * backoffMultiplier,
maxInterval
);
}
timeoutId = setTimeout(poll, currentInterval);
setInterval(currentInterval);
};
poll();
return () => clearTimeout(timeoutId);
}, [fetchFn, initialInterval, maxInterval, backoffMultiplier]);
return { data, hasNewData, currentInterval: interval };
}
React Query Polling
// Built-in refetch interval
function useOrderStatus(orderId) {
return useQuery({
queryKey: ['order', orderId],
queryFn: () => fetchOrder(orderId),
refetchInterval: (data) => {
// Stop polling when order is complete
if (data?.status === 'completed') return false;
return 3000;
},
refetchIntervalInBackground: false,
});
}
Best Practices
- Use WebSockets/SSE when available for real-time updates
- Implement smart polling with adaptive intervals
- Clean up intervals on component unmount
- Handle errors gracefully with backoff
- Consider bandwidth - don't poll too frequently
- Show connection status to users
Practice Problems
Create a reusable React component implementing Real-time Updates. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for Real-time Updates using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize Real-time Updates for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. What is the main advantage of WebSockets over polling?
2. When should you use Server-Sent Events?
3. What is smart polling?
4. Why add jitter to reconnection delays?
5. How should you handle WebSocket disconnection?
Flashcards
Question
What are WebSockets?
Click to reveal answer
Answer
A protocol for full-duplex, bidirectional communication between client and server over a single connection.
Question
What is Server-Sent Events (SSE)?
Click to reveal answer
Answer
A technology for server-to-client streaming over HTTP, with built-in reconnection.
Question
When should you use polling vs WebSockets?
Click to reveal answer
Answer
Use polling for infrequent updates; WebSockets for real-time, bidirectional communication.
Question
What is exponential backoff?
Click to reveal answer
Answer
A reconnection strategy that increases delay between attempts to avoid overwhelming the server.
Question
What is Real-time Updates?
Click to reveal answer
Answer
Real-time Updates is a key concept in frontend development.
Revision Notes
Key Takeaways
- 1.WebSockets provide bidirectional real-time communication
- 2.SSE is simpler for server-to-client streaming
- 3.Smart polling adapts interval based on data changes
- 4.Always implement reconnection logic for WebSockets
- 5.Clean up connections and intervals on component unmount
Interview Tips
- •Explain the difference between WebSockets and SSE
- •Discuss when to use polling vs WebSockets
- •Know how to handle reconnection and error recovery
Cheat Sheet
Real-time Updates Cheat Sheet
WebSockets
const ws = new WebSocket(url);
ws.onmessage = (e) => handleData(JSON.parse(e.data));
ws.send(JSON.stringify(data));
SSE
const es = new EventSource(url);
es.onmessage = (e) => handleData(JSON.parse(e.data));
Polling
setInterval(() => fetchData(), 5000);
// Clean up: clearInterval()
When to Use
- WebSockets: Chat, gaming, collaboration
- SSE: Notifications, live feeds, updates
- Polling: Fallback, simple use cases