robtaussig / react-use-websocket Goto Github PK
View Code? Open in Web Editor NEWReact Hook for WebSocket communication
License: MIT License
React Hook for WebSocket communication
License: MIT License
Hi,
I have a use case that a react component has to subscribe to multiple topics for real-time rendering. Let's say there is a web page that shows account balance for multiple bank accounts. Each update operation (for example PUT /account/:account_id/balance) will trigger a message sent to the react component that is using socket URL ws://localhost:8080/ws/account/1
. This way, the react component wants to subscribe to all accounts the user has. However, useWebSocket
or in general any react hook is not allowed to stay inside a for loop like below:
accountIds.forEach(accountId => {
const topic = `account:${accountId}`;
const [sendMessage, lastMessage, readyState, getWebSocket] = useWebSocket(
`${SOCKET_URL}/${topic}`
);
...
})
A walkaround I can think of: have the whole component subscribes to ws://localhost:8080/ws/accounts
, and on message received, check the account id. With this approach, the account id is sent in the payload rather than encoded as part of the socket URL.
What're your suggestions on the use case? Am I misusing your hook? Thanks
May I suggest exporting the ready state constants from the library. That would keep everyone from having to redefine them for themselves.
Similar to the following,
import { useWebSocket, ReadyState } from 'react-use-websocket';
if(readyState===ReadyState.CONNECTING) {
...
}
const [sendMessage, lastMessage, readyState] = useWebSocket(socketUrl);
I would assume first value reflects the state and the second one would be a callback
Examples:
const [state, setState] = useState(initialState);
...
const [state, dispatch] = useReducer(...);
That's my gut feeling, but I'm not sure if it is a convention
In this case, there are several states so it's a bit unclear what the value should be
How about?:
const [{data, status}, sendMessage] = useWebSocket(socketUrl);
Hey there,
I'm brand new to react and using this lib has been great, thanks for writing it.
One sharp edge that I've run into is that when I'm dev'ing with react-hot-loader and make a change I see the error
Error: The options object you pass must be static
I think this might be because when I make a change, the hot reloader re-renders the page (including recreating the WebSocket?) which causes react-use-websocket to think that the options object has changed? I'm not a 100% sure.
Any help would be greatly appreciated!
Thanks,
Geoff
In my situation, I need to temporarily close the connection and reconnect on the user's action. But native close()
is protected and I don't find a method to reconnect a closed connection except for the built-in auto reconnecting.
I am using v2.1.1 and get the following error on compile:
Argument of type 'string | null' is not assignable to parameter of type 'string | (() => string | Promise<string>)'.
Type 'null' is not assignable to type 'string | (() => string | Promise<string>)'. TS2345
95 |
96 | const {sendMessage, lastMessage, readyState} = useWebSocket(
> 97 | ctx.wsURL,
| ^
98 | wsOptions
99 | )
100 | console.debug(`Connecting to WebSocket at ${ctx.wsURL}`)
Here is what node_modules/react-use-websocket/dist/lib/use-websocket.d.ts
looks like:
import { Options, WebSocketHook } from './types';
export declare const useWebSocket: (url: string | (() => string | Promise<string>), options?: Options, connect?: boolean) => WebSocketHook<MessageEvent>;
And here is the declaration in node_modules/react-use-websocket/src/lib/use-websocket.ts
:
...
export const useWebSocket = (
url: string | (() => string | Promise<string>) | null,
options: Options = DEFAULT_OPTIONS,
connect: boolean = true,
): WebSocketHook => {
...
I don't know why null
is missing from the types in the dist
file.
Version: 1.3.4
When using a shared WebSocket connection, once the connection closes, no reconnection is attempted.
function WebSocket() {
const options = useMemo(() =>({
share: true,
shouldReconnect: (closeEvent) => true,
reconnectAttempts: 10,
reconnectInterval: 3000,
}, []);
const [sendMessage, lastMessage, readyState] = useWebSocket('wss://echo.websocket.org', options);
const status = {
0: 'CONNECTING', 1: 'OPEN', 2: 'CLOSING', 3: 'CLOSED',
}[readyState];
return <span>WebSocket status: {status}</span>
}
function App() {
return (
<React.Fragment>
<WebSocket />
<WebSocket />
</React.Fragment>
);
}
No reconnection is attempted - the connection stays closed.
The WebSocket should attempt reconnection. This works as expected when share
is false.
Suggestion; it would be great to have types (.d.ts) for this library
react-use-websocket/src/lib/socket-io.ts
Line 20 in 99d29db
a lib like socket.io-client provides an additional query
object when performing the handshake. This allows the client to pass alongside transports: "websocket"
additional params such as user_id: 9000
that the socket.io backend can consume.
Might it make sense to pass these along the options in the useSocketIo
hook instead of hardcoding them into the SOCKET_IO_PATH
constant?
Hi, I use useWebSocket with queryParams.
And I need to change parameter on user interface (user_id is query param).
When url contains variable params, it works.
But when using queryParams, it does not work.
Work
const [userId, setUserId] = useState(1)
useWebSocket(`wss://hostname/?user_id={userId}`, {
})
Not Work!
const [userId, setUserId] = useState(1)
useWebSocket(`wss://hostname/`, {
queryParams: {
user_id: userId,
},
})
The server sends a PING message to the client, which then need reply with PONG. Is there any good way to deal with this?
I apologize in advance if this is a dumb question, but I'm a hooks newbie, and am wondering how I'd use this react-use-websocket hook to perform the equivalent of this pseudo-code:
class Registration extends Component {
constructor(props) {
this.state = { registrationId: null };
}
componentDidMount() {
// For this example, just assume it's connected and returns a promise..
// The idea is that we request on mount, via websocket, a registration id.
this.props.ws.send({ action: 'register' }).then(({ id } => this.setState({ registrationId: id });
}
componentWillUnmount() {
// If we received an id on mount, unregister on unmount.
if (this.state.registrationId) {
this.props.ws.send({ action: 'unregister', id: this.state.registrationId });
}
}
}
Registrar.propTypes = {
ws: PropTypes.instanceOf(WebSocket),
};
Thanks for any assistance you can provide!
I am testing this library and how it handles reconnects. I'm using a slightly modified version of the example application from the project README.
On the face of it, the reconnects appear to work just fine, I see the socket state going to closed when I kill my server then back to open after my server restarts, and the subsequent messages are delivered properly.
However, I am concerned about what I see in the Chrome DevTools Network tab:
Each time I restart my server I see a new socket created, but an old one appears to hang around.
The ones that are "Finished" in that screenshot are the ones where the 5 second timeout between retries failed to reconnect.
Hi! Thanks for the custom hook!
I was wondering if there is a way to close a websocket connection when unmounting component (navigating to a different view)?
I tried to pass a function/promise, but it didn't work at all.
I used the online demo, and also the error was still there.
Error in /turbo_modules/react-use-websocket@1.1.0/dist/lib/create-or-join.js (12:47)
Failed to construct 'WebSocket': The URL 'function () {
return new Promise(function (resolve) {
setTimeout(function () {
resolve('wss://echo.websocket.org');
}, 2000);
});
}' is invalid.
can you please tell me what the problem is?
When using Ionic, pages may not be unmounted when switching between them.
As a result, the suggested mechanism to unsubscribe to data sources is to use Ionic lifecycle events instead of React methods useEffect and omponentWillUnmount. (docs).
As far as I understand, this is not compatible with the mechanism in this package, where the websocket connection is closed when the component is unmounted.
Is it possible to adapt this behavior to Ionic? Or is there any way to allow users to open/close connections on ionic lifecycle methods?
Should the regex line in the code below be this instead /^https|wss/.test(url);
The way it is now isSecure
would be true for urls prefixed by ws
which from my understanding is unsecure and will cause problems if you try to connect to an unsecure websocket server with the wss
prefix rather than ws
.
Why not try to implement "Options" as "Init Value," just like "useState" handles its argument.
Then if options change when rerendering, the changed options will be ignored. If so, we can remove that exception "The options object you pass must be static"
I have some problems with using your websocket on a single page in spa, setting options shared: ture, which is really just an instantiation, but I want to send Ping regularly at the root node.
Some pages subscribe to certain content when they want to load, and when they jump to another page, they cancel the subscription to the WS message.
Open is only executed once, and I can't just listen to ws. readyState === 1 and register again, so that every time I go in, I will execute it.
Finally, I hope the author will tell us some tips on how to use them. Thank you.
Hi,
I have a use case where:
However, at the very moment useWebSocket is triggered, whitelistedIds is empty, thus the filter does not work as expected.
An obvious solution is to not pass in the filter
and handle the filter logic inside the component that uses useWebSocket. It is simple and it works fine.
An obvious workaround is fetching whitelisted ids in the parent component, and pass it down as props. However, in my case, whitelisted ids are derived from route params passed into the component, and conceptually, its parent should not be aware of this.
Another workaround is to have options take a filter
with return type Promise
, but that makes the API complex and doesn't seem worth it.
I kinda prefer the 1st solution, what do you think maybe a better solution?
Current implementation of the globally declared const window: { ... }
throws error in compiler
ERROR in .../node_modules/react-use-websocket/dist/lib/socket-io.d.ts(3,11):
TS2451: Cannot redeclare block-scoped variable 'window'.
ERROR in .../node_modules/typescript/lib/lib.dom.d.ts(18212,13):
TS2451: Cannot redeclare block-scoped variable 'window'.
caused by:
declare global {
const window: {
location: {
protocol: string;
port: number;
hostname: string;
};
};
}
Simply removing the offending declaration allows it to compile, but I am unaware as to what use-case keeping it solves.
Alternatively, adjusting it to an interface resolves the compilation as well:
declare global {
interface Window {
location: Location;
}
}
In ReactNative, this is possible
const ws = new WebSocket(`${url}/trade/ws`, null, {
headers: { Authorization: `Bearer ${accessToken}` },
});
new (
uri: string,
protocols?: string | string[] | null,
options?: {
headers: { [headerName: string]: string };
[optionName: string]: any;
} | null,
): WebSocket;
But seems like browser's WebSocket API doesn't support. and also your awesome lib returns 401 response message when doesn't have this auth token in header.
Any tips?
Nice idea for a custom hook! Thanks for sharing!
In my use-case, my component must send a message in the onOpen event in order to register the filter criteria for its subscription. So far I've not been able to work out a way to do this because the onOpen event handler must be registered using the options at the time of useWebSocket creation, at which point the sendMessage function doesn't yet exist. Any ideas? :)
Figured this was worth a new issue
Hi,
How can we sendMessage when receiving onOpen event?
import useWebSocket from 'react-use-websocket';
const STATIC_OPTIONS = useMemo(() => ({
onOpen: (event) => {
console.log('opened');
// send a message to server
},
}), []);
const [sendMessage, lastMessage, readyState, getWebSocket] = useWebSocket('wss://echo.websocket.org', STATIC_OPTIONS);
We cannot use sendMessage
function because it will be defined after STATIC_OPTIONS memo and so sendMessage
cannot be placed into STATIC_OPTIONS dependency list (as eslint's exhaustive-deps says).
My first try is using event.currentTarget.send
, but I am not sure this is a good usage or not.
When async URL fetching fails, try to refetch the url in 'retryInterval' time again.
Why:
Using WebSocket token authentication (wsUrl returns a one time token)
Fetching fails for some reason (Like restarting the service, losing internet connection)
It should try to fetch it after 'retryInterval'
So I can reconnect once the service is up and running again.
I'd be willing to add this in a PR if you think this is a valid feature
https://stackblitz.com/edit/react-5tgwoe?file=index.js
when click on Close socket button onClose event does not fire either and readyState has not changed,
but in fact the connection is closed
Question: does this hook support a close event from the client side?
I would like to be able to close a websocket connection, even if it is shared.
The connection would be closed from one of the components using it.
The other components would handle it well: thanks to the readyState
flag they will know they can't send messages anymore, and they would not receive messages anymore as well.
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.)
Why are there such limitations? Can we remove them?
When using socket.io, I am emitting a message from the server:
io.emit('test', 'hello, world!')
but on the client (in the received MessageEvent), it results in:
data: "42["test","hello, world!"]"
or, in case of using an actual JSON payload:
io.emit('test', { message: 'hello, world!' })
on the client:
data: "42["test",{"message":"hello, world!"}]"
(Also, in case of using send
instead of emit
, the result is the same.)
which are both none-parseable as a JSON object. Is this expected or is this a a user error? My test setup is very minimal with the following config:
const Test = () => {
const options = useMemo(
() => ({
share: true,
fromSocketIO: true,
onMessage: (_event: MessageEvent) => {},
onClose: (_event: MessageEvent) => {},
onOpen: (_event: MessageEvent) => {},
onError: console.error,
}),
[],
)
const [sendMessage, lastMessage, readyState] = useWebSocket(
'ws://localhost:3000',
options,
)
console.log(lastMessage)
return (
<div />
)
}
Thanks in advance, AS
Hi there,
I'm trying to use the library connect to my mqtt broker but I can't find an option to send credential for my mqtt broker.
Any idea?
When dealing with websockets, it is common that messages are stringified objects.
Right now we can send such messages with the following:
const message = {
animal: 'cat',
color: 'white'
};
sendMessage(JSON.stringify(message))
It would be handy to have a method that stringifies the given object directly without decreasing readability due to using JSON.stringify
's everywhere:
const message = {
animal: 'cat',
color: 'white'
};
sendJsonMessage(message)
Something like lastJsonMessage
could also be exposed with an object directly, instead of having to JSON.parse()
everywhere
For instance, the library Sockette
proposes such a thing.
Other example, the library Socket.IO
even has this behavior of accepting an object by default through the method socket.emit()
There could be a boolean option.
If set to true sendMessage
will expect an object and lastMessage
will be an object.
If set to false (default to introduce a non-breaking change), sendMessage
will expect a string and lastMessage
will be a string.
const STATIC_OPTIONS = useMemo(() => ({
onOpen: () => console.log('opened'),
messagesAreJsonObjects: => true
}), []);
Hi,
I'm trying to validate a JWT token in the server side and when returning a 401 response how do I handle it at the frontend?
sendMessage(
JSON.stringify({
text: event.target.value,
token
})
);
What I've tried so far.
const { sendMessage, lastMessage, readyState } = useWebSocket(
"ws://localhost:3333",
{
reconnectAttempts: 10,
reconnectInterval: 3000,
onError: (error) => {
console.log(error);
},
}
);
Did not log the error.
It is not really clear how to do that now. I suppose you can call sendMessage in the options object. But is it better way ? Thanks
It would be cool if the first url
param could optionally be an async function that returns a URL, similar to what's found in this package https://www.npmjs.com/package/reconnecting-websocket#update-url.
My use case is similar to the example they give, fetching a short-lived auth token:
const urlProvider = async () => {
const token = await getSessionToken();
return `wss://my.site.com/${token}`;
};
The online demo link: https://robtaussig.com/socket/
is broken: Uncaught TypeError: Object(...) is not a function
See #7
This hook has many return values to remember the right order. A object would be more appropriate, reducing the coupling between the return values and the hook usage.
https://stackblitz.com/edit/react-h845um?file=index.js
onOpen event in this example works twice, although the connection is only one
I need setLastMessage
or clearLastMessage
.
I want to change parameter in url and reconnect.
So lastMessage at old param is not good.
It should be null.
So...
`
const [sendMessage, lastMessage, getWebSocket] = useWebSocket(
socketUrl,
SOCKET_OPTIONS
);
useEffect(() => {
setTimeout(() => {
getWebSocket().close();
}, 3000);
}, []);
`
browser console:
Uncaught TypeError:getWebSocket is not a function
Hi, I'm trying to use this library for my websocket needs. I am trying to handle reconnects myself. This is mainly because the URL has time sensitive tokens.
So I set these options:
retryOnError: false,
shouldReconnect: (closeEvent) => false,
Then, on my test laptop, I turn off the wi-fi for like 30 seconds. When I turn it back on, not only does it try to reconnect the first socket, but it also tries to reconnect with an all-new socket, so now I have two socket connections to my server. What am I doing wrong?
It would be great to add an option to open the websocket by default. Or not open it by default.
For instance, I would like users to do a specific action - let's say click on a button, before they connect to the websocket and start sending/receiving messages.
I do not want to open a websocket connection before users have clicked this button.
Sometimes they do not even click the button.
const STATIC_OPTIONS = useMemo(
() => ({
onOpen: () => console.log('socket opened'),
connectImmediately: false, // default: true
}),
[]
);
const [sendMessage, lastMessage, readyState, getWebSocket] = useWebSocket(
config.WEBSOCKETS_URL,
STATIC_OPTIONS
);
const onButtonClick = () => {
getWebSocket().open();
}
--
It would also be usable in a shared websocket, #43
useWebSocket
checks for url !== null
but it's signature does not allow urls to be null.
Could the signature be changed to string | (() => string | Promise<string>) | null
to match the implementation?
Currently, the only way of "reconnecting" can be done by rerendering the component and so the hook by changing the URL.
I'd like to have an option whether the connection should try n
reconnect attempts after the connection has been closed (e.g. the server shut down unexpectedly). I'm fine with contributing by a PR, but currently, I've no good idea how to add this into your implementation, do you have?
Hello,
First of all great library and very useful. So, I'm getting a lot of this type of error A subscriber that is no longer registered has attempted to unsubscribe
and wanted to know why? I saw the source code but still can't understand. Do I have to use the same options for every shared web socket?
Example of first initialized web socket, this is called in my main app file.
const { sendJsonMessage, lastJsonMessage } = useWebSocket(WS_URL, {
share: true,
shouldReconnect: (closeEvent) => closeEvent.code === WS_CLOSED,
// will attempt to reconnect 20 * WS_RECONNECT_INTERVAL_MS
reconnectAttempts: WS_RECONNECT_ATTEMPTS,
reconnectInterval: WS_RECONNECT_INTERVAL_MS,
retryOnError: true,
});
And I have some other web socket in components like this:
const { lastJsonMessage } = useWebSocket(WS_URL, {
share: true,
});
Thanks for your help and for the library 🚀
There is another issue. I am using several wrapper components that are responsible for different data fetching / processing tasks, each using useWebsocket hook with a shared socket (share={true}
).
<FirstUseWebsocketComponent>
<Child>
<SecondUseWebcoksetComponent>
<AnotherChild />
</SecondUseWebcoksetComponent>
</Child>
</FirstUseWebsocketComponent>
Inside the SecondUseWebsocket component I try to fetch some data after the component mounts
const onMessage = (e: any) => console.log(e);
const { sendMessage, sendJsonMessage, readyState } = useWebSocket(
socketUrl,
{
share: true,
onMessage,
},
isVisible
);
useEffect(() => {
if (readyState === 1) {
console.log("EFFECT CALLED");
sendJsonMessage({ action: "getPlayerState" });
sendJsonMessage({ action: "getMessages" });
}
}, [readyState]);
The component mounts and the effect is called, however on inspecting the websocket in chrome dev tools, I see that no message is sent. However if I wrap the call to sendJsonMessage inside a setTimeout the message gets send, so I guess there is some sort of race condition going on.
Thank you for the work you put into this. I would like to connect to a socket that requires authentication (AWS WebSocket API Gateway). If I provide the wrong credentials and the connection is rejected, I can see the error message in the console, but it seems it is not exposed in the hook
WebSocket connection to 'wss://myuri/?Authorization=SOMEAUTH' failed: HTTP Authentication failed; no valid credentials available
Would it be possible to access the error message so I can adapt the UI based on whether there is a network error or invalid credentials?
Hi,
I am building a POC using the hook in the frontend together with a simple backend built with golang and gorilla/websocket.
The issue I find out: every time I refresh the web page, a new websocket connection is created, and all these websockets is connecting the same socket url. It leads to: when the backend publishes a message “data” (code), it will be sent to all these websockets on the frontend. Here are steps to reproduce:
yarn && yarn start
go run *.go
(it assumes go environment setup)curl -X POST http://localhost:8080/account/1/deposit/10
to increase balance for account 1received message "data" from ws://localhost:8080/ws
. on the go console, it logs Received request POST /account/1/deposit/10
, client
, Received request GET /account/1/balance
. (the “client” means the golang backend is sending message to 1 client)received message "data\ndata" from ws://localhost:8080/ws
. on the go console, it logs client
twice, which means the backend sends messages to 2 clients.Question: how to avoid creating a new websocket per page refresh? If this is hard to achieve, I hope the web page receive message “data” rather than “data\ndata”, how can i do it?
Let me know if you need more details to troubleshoot. Thank you!
It is not possible to set binaryType for a websocket object.
It would be good to have a feature that allows to set a value for this.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.