Subscribing to rooms from React

The backend WebSocket phase taught you how rooms work on the server side. On the client, the pattern is the same: open the connection, send a subscribe message with a channel name, receive only the messages for that channel. React makes it idiomatic with a useEffect that opens and closes the subscription as the component mounts and unmounts.

src/components/LiveCatalog.tsx
tsx
'use client';

import { useEffect, useState } from 'react';

export function LiveCatalog({ token, initialBooks }: { token: string; initialBooks: Book[] }) {
  const [books, setBooks] = useState(initialBooks);

  useEffect(() => {
    const ws = new WebSocket('ws://localhost:3000?token=' + token);

    ws.onopen = () => {
      ws.send(JSON.stringify({ type: 'subscribe', channel: 'catalog' }));
    };

    ws.onmessage = (event) => {
      const msg = JSON.parse(event.data);
      if (msg.type === 'book_added') {
        setBooks((prev) => [...prev, msg.data]);
      }
    };

    return () => ws.close();
  }, [token]);

  return books.map((b) => <BookCard key={b.id} book={b} />);
}

A live catalog that subscribes to the catalog room and updates on any server push.

Notice the return function in the effect. It calls ws.close() when the component unmounts. Without that cleanup, every time the component re-renders you leak a WebSocket connection and eventually the server stops accepting new ones. Cleanup is non-negotiable for long-lived connections.

Quiz: Quiz

Loading practice…