Coder Social home page Coder Social logo

react-use-websocket's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-use-websocket's Issues

With respect to instantiating only one ws, flexible subscription and cancellation of WS messages on some pages

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.
image
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.

Block-scoped window redeclaration not allowed

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;
    }
}

Possible to send Authorization header?

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?

[QUESTION] Getting `A subscriber that is no longer registered has attempted to unsubscribe`

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 🚀

Do not reconnect when queryParams value changed.

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,
    },
})

Expose error message

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?

Handle getWsUrl failiure simmilar to WS failiures

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

Expose readyState constants enum

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) {
  ...
}

How to avoid new websocket connection per webpage refresh

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:

  1. Use this branch https://github.com/YikSanChan/react-golang-websockets-bankaccounts/tree/frontend-ws-filter
  2. go to /frontend, yarn && yarn start
  3. go to /backend, go run *.go (it assumes go environment setup)
  4. on the browser, visit http://localhost:3000/1, should find “Bank account 1; the websocket is currently open”
  5. on the terminal, curl -X POST http://localhost:8080/account/1/deposit/10 to increase balance for account 1
  6. on the browser console, it logs received 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)
  7. refresh the browser, and repeat step 5
  8. on the browser console, it logs 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!

websocket auto-reconnects anyway

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?

filter received messages by the result of useEffect

Hi,

I have a use case where:

  • The component mounts
  • const [whitelistedIds, setWhitelistedIds] = useState([])
  • useWebSocket(url, {filter: msg => whitelistedIds.contains(parse(msg))})
  • useEffect is triggered after the first render, which pulls whitelistedIds and setWhitelistedIds

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?

How to close socket

`
const [sendMessage, lastMessage, getWebSocket] = useWebSocket(
socketUrl,
SOCKET_OPTIONS
);

useEffect(() => {
setTimeout(() => {
getWebSocket().close();
}, 3000);
}, []);
`

browser console:
Uncaught TypeError:getWebSocket is not a function

Hooks equivalent of this class-based mount/unmount?

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!

reorder useWebSocket return values?

  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);

Problem with order of return values

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.

websocket.onmessage

Can the time consumed by the websocket.onmessage function be optimized? It feels like a bit of a performance impact, because there are many functions nested to execute.
image
Some cost 20 to 40 ms

Reconnects leaking sockets?

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:

image

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.

Include types

Suggestion; it would be great to have types (.d.ts) for this library

Websocket messages not sent when using a shared

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.

Set binaryType

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.

Error when using react-hot-loader

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

Async url provider

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}`;
};

Handle ping/pong

The server sends a PING message to the client, which then need reply with PONG. Is there any good way to deal with this?

Ionic: Close connection on page change.

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?

null URL not actually allowed

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.

Bug: does not reconnect when using a shared WebSocket

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>
  );
}

The current behaviour

No reconnection is attempted - the connection stays closed.

The expected behaviour

The WebSocket should attempt reconnection. This works as expected when share is false.

Add option to retry reconnection after the web socket connection has been closed

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?

Failed to construct URL from URL and Promise

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?

Sending message in onOpen

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.

Allow shared websockets to be closed

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?

Send message on connection

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? :)

How to manually close and reconnect a socket?

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.

useWebSocket in a for loop

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

Cannot JSON.parse received data when using socket.io

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

setLastMessage or clearLastMessage

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...

  1. when url change, clear lastMessage
  2. return clearLastMessage function at useWebSocket

Ease the use of json messages

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))

Proposal A:

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()

Proposal B

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
}), []);

Add an option to immediately open the websocket connection (or not)

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

About "Static Options"

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"

Not hardcoding query params for socket-io handshake

return `${protocol}://${removedFinalBackSlack}${SOCKET_IO_PATH}`;

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?

How to Handle sendMessage error response?

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.