Skip to content
intermediatePhase 37 · Frontend Architecture

Real-time Updates

Implement WebSockets, SSE, and polling for live data updates.

1h
0 problems
Topic Progress0%

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

  1. Use WebSockets/SSE when available for real-time updates
  2. Implement smart polling with adaptive intervals
  3. Clean up intervals on component unmount
  4. Handle errors gracefully with backoff
  5. Consider bandwidth - don't poll too frequently
  6. Show connection status to users

Practice Problems

0/3solved
Build Real-time Updates Component

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 needed
Real-time Updates Testing

Write 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 tests
Real-time Updates Performance

Optimize 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 analysis

Quiz

1. What is the main advantage of WebSockets over polling?

Question 1 options

2. When should you use Server-Sent Events?

Question 2 options

3. What is smart polling?

Question 3 options

4. Why add jitter to reconnection delays?

Question 4 options

5. How should you handle WebSocket disconnection?

Question 5 options

Flashcards

Question

What are WebSockets?

Answer

A protocol for full-duplex, bidirectional communication between client and server over a single connection.

Question

What is Server-Sent Events (SSE)?

Answer

A technology for server-to-client streaming over HTTP, with built-in reconnection.

Question

When should you use polling vs WebSockets?

Answer

Use polling for infrequent updates; WebSockets for real-time, bidirectional communication.

Question

What is exponential backoff?

Answer

A reconnection strategy that increases delay between attempts to avoid overwhelming the server.

Question

What is Real-time Updates?

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