Note: wss://demos.kaazing.com/echo has been down lately, so the demo will fail to connect when using that as the endpoint. On the plus side, this demonstrates the behavior of a connection failure.
React Hook designed to provide robust WebSocket integrations to your React Components. Experimental support for SocketIO (read documentation below for more information)
Pull requests welcomed!
useWebSocket now returns an object instead of an array. This allows you to pick out specific features/properties to suit your use-case as well as removing mental overhead of keeping track of item order.lastJsonMessage and sendJsonMessage added to return value to reduce need to stringify and parse outgoing and incoming messages at the component level.false as the third parameter. This provides a more explicit solution than the previous method of setting the socketUrl to null. Both methods work and are supported usage.import React, { useState, useCallback, useEffect } from 'react';
import useWebSocket, { ReadyState } from 'react-use-websocket';
export const WebSocketDemo = () => {
//Public API that will echo messages sent to it back to the client
const [socketUrl, setSocketUrl] = useState('wss://echo.websocket.org');
const [messageHistory, setMessageHistory] = useState([]);
const { sendMessage, lastMessage, readyState } = useWebSocket(socketUrl);
useEffect(() => {
if (lastMessage !== null) {
setMessageHistory((prev) => prev.concat(lastMessage));
}
}, [lastMessage, setMessageHistory]);
const handleClickChangeSocketUrl = useCallback(
() => setSocketUrl('wss://demos.kaazing.com/echo'),
[]
);
const handleClickSendMessage = useCallback(() => sendMessage('Hello'), []);
const connectionStatus = {
[ReadyState.CONNECTING]: 'Connecting',
[ReadyState.OPEN]: 'Open',
[ReadyState.CLOSING]: 'Closing',
[ReadyState.CLOSED]: 'Closed',
[ReadyState.UNINSTANTIATED]: 'Uninstantiated',
}[readyState];
return (
<button onClick={handleClickChangeSocketUrl}>
Click Me to change Socket Url
</button>
<button
onClick={handleClickSendMessage}
disabled={readyState !== ReadyState.OPEN}
>
Click Me to send 'Hello'
</button>
<span>The WebSocket is currently {connectionStatus}</span>
{lastMessage ? <span>Last message: {lastMessage.data}</span> : null}
<ul>
{messageHistory.map((message, idx) => (
<span key={idx}>{message ? message.data : null}</span>
))}
</ul>
);
};
From the example above, the component will rerender every time the readyState of the WebSocket changes, as well as when the WebSocket receives a message (which will change lastMessage). sendMessage is a memoized callback that will pass the message to the current WebSocket (referenced to internally with useRef).
A demo of this can be found here. Each component uses its own useWebSocket hook. This implementation takes advantage of passing an optional options object (documented below). Among setting event callbacks (for onmessage, onclose, onerror, and onopen) that will log to the console, it is using the share option -- if multiple components pass the same socketUrl to useWebSocket and with share set to true, then only a single WebSocket will be created and useWebSocket will manage subscriptions/unsubscriptions internally. useWebSocket will keep track of how many subscribers any given WebSocket has and will automatically free it from memory once there are no subscribers remaining (a subscriber unsubscribes when it either unmounts or changes its socketUrl). Of course, multiple WebSockets can be created with the same target url, and so components are not required to share the same communication pipeline.
npm install react-use-websocket
import useWebSocket from 'react-use-websocket';
// In functional React component
// This can also be an async getter function. See notes below on Async Urls.
const socketUrl = 'wss://echo.websocket.org';
const {
sendMessage,
sendJsonMessage,
lastMessage,
lastJsonMessage,
readyState,
getWebSocket,
} = useWebSocket(socketUrl, {
onOpen: () => console.log('opened'),
//Will attempt to reconnect on all close events, such as server shutting down
shouldReconnect: (closeEvent) => true,
});
type UseWebSocket = (
//Url can be return value of a memoized async function.
url: string | () => Promise<string>,
options: {
fromSocketIO?: boolean;
queryParams?: { [field: string]: any };
protocols?: string | string[];
share?: boolean;
onOpen?: (event: WebSocketEventMap['open']) => void;
onClose?: (event: WebSocketEventMap['close']) => void;
onMessage?: (event: WebSocketEventMap['message']) => void;
onError?: (event: WebSocketEventMap['error']) => void;
onReconnectStop?: (numAttempts: number) => void;
shouldReconnect?: (event: WebSocketEventMap['close']) => boolean;
reconnectInterval?: number;
reconnectAttempts?: number;
filter?: (message: WebSocketEventMap['message']) => boolean;
retryOnError?: boolean;
eventSourceOptions?: EventSourceInit;
} = {},
shouldConnect: boolean = true,
): {
sendMessage: (message: string, keep: boolean = true) => void,
//jsonMessage must be JSON-parsable
sendJsonMessage: (jsonMessage: any, keep: boolean = true) => void,
//null before first received message
lastMessage: WebSocketEventMap['message'] | null,
//null before first received message. If message.data is not JSON parsable, then this will be a static empty object
lastJsonMessage: WebSocketEventMap['message']['data'] | null,
// -1 if uninstantiated, otherwise follows WebSocket readyState mapping: 0: 'Connecting', 1 'OPEN', 2: 'CLOSING', 3: 'CLOSED'
readyState: number,
// If using a shared websocket, return value will be a proxy-wrapped websocket, with certain properties/methods protected
getWebSocket: () => (WebSocket | null),
}
Instead of passing a string as the first argument to useWebSocket, you can pass a function that returns a string (or a promise that resolves to a string). It's important to note, however, that other rules still apply -- namely, that if the function reference changes, then it will be called again, potentially instantiating a new WebSocket if the returned url changes.
import useWebSocket from 'react-use-websocket';
// In functional React component
const getSocketUrl = useCallback(() => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('wss://echo.websocket.org');
}, 2000);
});
}, []);
const { sendMessage, lastMessage, readyState, getWebSocket } = useWebSocket(
getSocketUrl,
STATIC_OPTIONS
);
type sendMessage = (message: string, keep: boolean = true) => void;
The argument sent through sendMessage will be passed directly to WebSocket#send. sendMessage will be static, and thus can be passed down through children components without triggering prop changes. Messages sent before the WebSocket is open will be queued up and sent on connection. If you don't want to use messages queue for a particular message you should use a 'keep' parameter.
type sendJsonMessage = (message: any, keep: boolean = true) => void;
Message will first be passed through JSON.stringify.
type lastMessage = WebSocketEventMap['message'];
Will be an unparsed MessageEvent received from the WebSocket.
type lastMessage = any;
A JSON.parsed object from the lastMessage. If lastMessage is not a valid JSON string, lastJsonMessage will be an empty object.
enum ReadyState {
UNINSTANTIATED = -1,
CONNECTING = 0,
OPEN = 1,
CLOSING = 2,
CLOSED = 3,
}
Will be an integer representing the readyState of the WebSocket. -1 is not a valid WebSocket readyState, but instead indicates that the WebSocket has not been instantiated yet (either because the url is null or connect param is false)
type getWebSocket = () => WebSocket | Proxy<WebSocket>;
If the WebSocket is shared, calling this function will lazily instantiate a Proxy instance that wraps the underlying WebSocket. You can get and set properties on the return value that will directly interact with the WebSocket, however certain properties/methods are protected (cannot invoke close or send, and cannot redefine any of the event handlers like onmessage, onclose, onopen and onerror. An example of using this:
const { sendMessage, lastMessage, readyState, getWebSocket } = useWebSocket(
'wss://echo.websocket.org',
{ share: true }
);
useEffect(() => {
console.log(getWebSocket().binaryType);
//=> 'blob'
//Change binaryType property of WebSocket
getWebSocket().binaryType = 'arraybuffer';
console.log(getWebSocket().binaryType);
//=> 'arraybuffer'
//Attempt to change event handler
getWebSocket().onmessage = console.log;
//=> A warning is logged to console: 'The WebSocket's event handlers should be defined through the options object passed into useWebSocket.'
//Attempt to change an immutable property
getWebSocket().url = 'www.google.com';
console.log(getWebSocket().url);
//=> 'wss://echo.websocket.org'
//Attempt to call webSocket#send
getWebSocket().send('Hello from WebSocket');
//=> No message is sent, and no error thrown (a no-op function was returned), but an error will be logged to console: 'Calling methods directly on the WebSocket is not supported at this moment. You must use the methods returned by useWebSocket.'
}, []);
If the WebSocket is not shared (via options), then the return value is the underlying WebSocket, and thus methods such as close and send can be accessed and used.
By default, useWebSocket will not attempt to reconnect to a WebSocket. This behavior can be modified through a few options. To attempt to reconnect on error events, set Options#retryOnError to true. Because CloseEvents are less straight forward (e.g., was it triggered intentionally by the client or by something unexpected by the server restarting?), Options#shouldReconnect must be provided as a callback, with the socket CloseEvent as the first and only argument, and a return value of either true or false. If true, useWebSocket will attempt to reconnect up to a specified number of attempts (with a default of 20) at a specified interval (with a default of 5000 (ms)). The option properties for attempts is Options#reconnectAttempts and the interval is Options#reconnectInterval. As an example:
const didUnmount = useRef(false);
const [sendMessage, lastMessage, readyState] = useWebSocket(
'wss://echo.websocket.org',
{
shouldReconnect: (closeEvent) => {
/*
useWebSocket will handle unmounting for you, but this is an example of a
case in which you would not want it to automatically reconnect
*/
return didUnmount.current === false;
},
reconnectAttempts: 10,
reconnectInterval: 3000,
}
);
useEffect(() => {
return () => {
didUnmount.current = true;
};
}, []);
interface Options {
share?: boolean;
shouldReconnect?: (event: WebSocketEventMap['close']) => boolean;
reconnectInterval?: number;
reconnectAttempts?: number;
filter?: (message: WebSocketEventMap['message']) => boolean;
retryOnError?: boolean;
onOpen?: (event: WebSocketEventMap['open']) => void;
onClose?: (event: WebSocketEventMap['close']) => void;
onMessage?: (event: WebSocketEventMap['message']) => void;
onError?: (event: WebSocketEventMap['error']) => void;
onReconnectStop?: (numAttempted: number) => void;
fromSocketIO?: boolean;
queryParams?: {
[key: string]: string | number;
};
protocols?: string | string[];
eventSourceOptions?: EventSourceInit;
}
See section on Reconnecting.
Number of milliseconds to wait until it attempts to reconnect. Default is 5000.
Each of Options#onMessage, Options#onError, Options#onClose, and Options#onOpen will be called on the corresponding WebSocket event, if provided. Each will be passed the same event provided from the WebSocket.
If provided in options, will be called when websocket exceeds reconnect limit, either as provided in the options or the default value of 20.
If set to true, a new WebSocket will not be instantiated if one for the same url has already been
$ claude mcp add react-use-websocket \
-- python -m otcore.mcp_server <graph>