Performance
This page explains when components re-render, how to handle high-frequency streams, and what the library costs in bundle size.
What re-renders when
useWebSocket is the convenient choice for chat, notifications and other streams of a few messages per second. For market data, telemetry, cursors or anything that can send dozens of messages per second, split the work:
useWebSocketStatus(client)for the connection indicator.useWebSocketEvent(client, 'message', listener)to receive messages without rendering, and update state on your own schedule.- Or reduce messages into a store with
createWebSocketState, and let each component select only its slice withuseWebSocketState. With 500 symbols streaming, a<Price symbol="ACME" />re-renders only when ACME changes.
Batching a fast stream
React already batches state updates made in the same task, but each WebSocket message is its own task, so every message is a render. Reduce the stream into a store with batch: 'frame', and its readers render at most once per animation frame:
A few things to note:
- Messages are still reduced as they arrive, so
prices.getState()is always current. Only the renders wait for the frame. - Browsers pause animation frames in background tabs. The store keeps up, and the list renders the latest prices when the tab is visible again.
- To throttle further, say to four renders a second, buffer in a
useWebSocketEventlistener and flush withsetTimeout(…, 250). - If you only need the latest value and never render it (say, to send it with the next order), keep it in a ref and skip state entirely.
Expensive derived UI
When a message triggers expensive rendering (a big chart, a filtered table), keep the input responsive by marking that work as non-urgent.
Wrap the state update in startTransition:
Or defer the expensive child with useDeferredValue:
Keep updates that must feel instant, like a connection indicator or the message the user just sent, outside of transitions.
To see these in a whole app, see Build a live trading dashboard: 500 symbols, measured with and without useInView and batching.
React Compiler and memoization
The hooks are compatible with the React Compiler and with hand-written memoization:
- Handlers don't need
useCallback.onMessage,onOpen,onClose,onErroranduseWebSocketEventlisteners always see your latest props and state, and passing a new function never reconnects. - The options object doesn't need
useMemo. For URL hooks, connection options are read once, when the shared client is created. - Returned actions are stable.
send,closeandreconnectfromuseWebSocketkeep the same identity across renders, so you can pass them to memoized children or list them in effect dependencies. - Snapshots are immutable.
getSnapshot()returns the same object until something changes, asuseSyncExternalStorerequires.
Bundle size
Sizes are measured by size-limit in CI, with budgets that fail the build if they grow. React is a peer dependency and not counted.
- Zero dependencies. Nothing else is installed with the package.
- Tree-shakeable. The package is marked
"sideEffects": falseand ships ES modules, so bundlers drop what you don't import. react-websocket/corehas no React import at all; see Without React.