Real-Time Data in React
AdvancedA socket delivers events; your UI needs state. Bridging them means one connection per app rather than per component, and writing updates into the same cache your queries read.
Overview
Live data arrives as a stream of events, and the naive approach — a WebSocket per component, pushing into local state — produces duplicate connections, state that disagrees with the fetched data, and a reconnect story nobody wrote. The pattern that works is to treat the socket as one shared connection at the top of the app, and to have its messages update the query cache rather than a parallel copy of the data. Then every component that already reads that query gets the live value for free, and the initial fetch and the live updates cannot diverge.
One Connection, Shared
Establish it once, at the top, and expose it through context.
// A socket per component means N connections and N reconnect loops.
// One provider, near the root, is the shape you want.
function RealtimeProvider({ children }) {
const queryClient = useQueryClient()
useEffect(() => {
const ws = new WebSocket(WS_URL)
ws.onmessage = (e) => {
const event = JSON.parse(e.data)
handle(event, queryClient)
}
ws.onclose = (e) => { if (!e.wasClean) scheduleReconnect() }
return () => ws.close() // cleanup, always
}, [queryClient])
return children
}
// queryClient is stable, so this effect runs once — and because the
// handler writes to the cache rather than to component state, no
// component needs to know the socket exists.
// For one-way updates, prefer SSE: it reconnects by itself and
// needs no heartbeat. See the JavaScript track's streaming concept.Events Into the Cache
Two options per event — patch the cache, or invalidate and refetch.
function handle(event, queryClient) {
switch (event.type) {
// Small, self-contained payload -> patch directly, no request
case 'submission.updated':
queryClient.setQueryData(['submission', event.id], event.data)
break
// Anything where the event does not carry the full new shape
// -> mark stale and let the query refetch the truth
case 'leaderboard.changed':
queryClient.invalidateQueries({ queryKey: ['leaderboard'] })
break
}
}
// Patching is faster; invalidating is safer. When the event payload
// is partial, patching produces an object missing fields the UI
// expects — invalidate instead.
// Out-of-order and duplicate events are normal on a reconnect.
// If the payload carries a version or updatedAt, ignore anything
// older than what the cache already holds.Reconnects and Volume
The two things that break live UIs in production.
// On reconnect, you missed events. Refetch rather than assume.
ws.onopen = () => {
queryClient.invalidateQueries() // resync everything that is live
}
// Backoff, so a dead server does not get hammered by every client
let attempt = 0
function scheduleReconnect() {
const delay = Math.min(1000 * 2 ** attempt++, 30_000)
setTimeout(connect, delay + Math.random() * 500) // jitter
}
// High-frequency streams will re-render the app on every tick.
// Buffer and flush on an interval instead:
const buffer = useRef([])
useEffect(() => {
const id = setInterval(() => {
if (!buffer.current.length) return
applyAll(buffer.current)
buffer.current = []
}, 250)
return () => clearInterval(id)
}, [])
// A price ticker at 50 messages a second must not be 50 renders a
// second — that is the difference between a live UI and a frozen tab.Key Points to Remember
- 1Open one shared connection near the root rather than one per component
- 2Write live events into the query cache so fetched data and live data cannot diverge
- 3Patch the cache when the event carries the full shape; invalidate when it does not
- 4On reconnect you have missed events — resync by invalidating rather than assuming continuity
- 5Reconnect with exponential backoff plus jitter, and buffer high-frequency events instead of rendering each one
Interview Questions
Sign in to ask AriaHow do you keep live socket updates consistent with data you fetched over HTTP?
Why open the WebSocket in a provider rather than in the component that displays the data?
A stream delivers 50 messages a second. How do you stop the UI freezing?
Ask Aria about Real-Time Data in React
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.