Topic subscriptions
Many servers multiplex one socket into topics or channels: prices:ACME, room:42, user:7:notifications. You send a subscribe message to start receiving one and an unsubscribe message to stop. This page shows how to manage those subscriptions so they're shared between components and survive reconnects.
The reconnect problem
A server forgets a connection's subscriptions when the connection drops. After the client reconnects, the socket is open again, the status says open, and no data arrives, because nobody subscribed on the new connection. It's one of the most common WebSocket bugs, and it usually shows up in production as "the dashboard froze".
createWebSocketTopics fixes it: it remembers which topics are in use and subscribes to all of them again every time the socket opens.
Setup
Describe your server's messages once, next to the client:
The messages are checked against the client's Out type. Leave out unsubscribe if your server has no such message.
Subscribing from components
useWebSocketTopic holds a topic while the component is mounted:
- Shared. Two
<Ticker symbol="ACME" />components send onesubscribe. Theunsubscribegoes out when the last one unmounts. - Follows props. Changing
symbolsubscribes to the new topic and unsubscribes from the old one. - Holds the connection. You don't need another hook to keep the socket open.
- Optional. Pass
nullto hold nothing.
Pass an array to hold several topics. A new array with the same topics doesn't resubscribe:
To keep the received data in one place instead of in each component, combine topics with message state.
What gets sent, and when
Subscriptions are never put in the send queue. On open, they're sent before messages you queued while disconnected, so those messages reach a server that already knows your subscriptions. While the server is closing the socket, which still reads open until its close event, nothing is sent: the next connection subscribes to what's held then.
The one-tick delay before unsubscribe is the same trick the client uses for connections. StrictMode's mount → unmount → mount, <Activity> toggles and route transitions don't send unsubscribe + subscribe pairs.
open listeners run in the order they were added, and topics subscribe from one. If your server needs an auth message before anything else, add your auth listener (live.on('open', …)) before calling createWebSocketTopics. See First-message auth.
Numbered feeds
Resubscribing fixes the frozen dashboard, but it can't say what happened while the connection was down. A chat room, a scoreboard or an order book needs more: every message, exactly once, in order. If the server numbers the messages on each topic, createWebSocketFeeds gives you that. It's a topics manager whose subscribe message says where you left off:
Components hold topics exactly as before, with useWebSocketTopic(feeds, 'room:42'). What changes is what reaches room:
- After a reconnect, each held topic is subscribed with
after, the number of the last message received, so the server sends only what was missed. - Duplicates are dropped. A message numbered at or below the last one never reaches your reducer, even if the server replays more than it needs to.
- Gaps are filled. A message that skips numbers is held back, and the feed asks the server once for what's missing (
resume, which defaults to subscribing again withafter). Nothing is delivered out of order. - Snapshots are the fallback. When the server no longer has the messages after
after, or it restarted and its numbers are behind the client's, it sends the topic's whole state with its current number. The snapshot is always delivered, and numbering goes on from it.
Messages that aren't numbered, and those on topics the feed doesn't hold, pass through untouched, so feeds can stand in for the client wherever you listen to messages: feeds.on('message', …) or createWebSocketState(feeds, …).
The server keeps a short history per topic. With the ws package, the subscribe handler looks like this:
See createWebSocketFeeds for every option.
Outside React
topics.acquire(topic) does what the hook does and returns a release function:
topics.active() lists the topics currently held, which is handy in tests and debugging.
See also
createWebSocketTopics,createWebSocketFeedsanduseWebSocketTopic- Message state
- Reconnecting and heartbeats
- Build a team chat app: numbered feeds for channels, with unread counts and catching up after a reconnect
- Build a live trading dashboard: quotes as topics and an order book as a feed, in a whole app