1

I am trying to use websockets to connect to Kraken websocket API. I create a new instance and setup listeners on intitial render. I am trying to make buttons to subscribe and close but the buttons are not working.

I am using this websocket library.

const WebSocket = require('isomorphic-ws')
   

function App() {

  const ws = new WebSocket('wss://ws.kraken.com')
  
  useEffect(() => {

    ws.onopen = () => {
      console.log('connected')
    }

    ws.onmessage = (msg) => {
      const message = JSON.parse(msg.data)
      const sub = message[2]
      console.log(message)
    }

    ws.onclose = () => {
      console.log('closing connection')
      // ws.close()
    }

    return () => {
      ws.close()
    }
  }, [])

 const wsClose = () => {
   ws.close()
 }

  const wsSub = () => {
    ws.send(JSON.stringify(
      {
        "event": "subscribe",
        "pair": [
          "XBT/USD"
        ],
        "subscription": {
          "name": "ticker"
        }
      }
    ))
    console.log('send subscribe')
  }

  return (
    <button onClick={wsSub}>subscribe</button>
    <button onClick={wsClose}>close ws</button>
  )
}

export default App

If I put the code from the wsSub function under the ws.onopen listener the subscription works, but I want to have control over the subscriptions not subscribe when the websocket is opened. I am using buttons like this for testing. I want to subscribe and unsubscribe based on user form submission but I feel like I should be able to get this working first.

1 Answer 1

1

Right now, you're creating a new socket every time the component re-renders. The effect callback references the first socket created (on mount), but the wsClose and wsSub do not (they reference the socket created in the immediately previous render).

Put the socket into a state or a ref:

const wsRef = useRef();
if (!wsRef.current) {
  wsRef.current = new WebSocket('wss://ws.kraken.com');
}

Then proceed to replace all uses of ws with wsRef.current.

Sign up to request clarification or add additional context in comments.

4 Comments

Would it be better to create the socket in a different way? Would it make a difference if it was outside the App function or inside the useEffect?
If you're sure it's always (and only ever) going to be instantiated exactly once whenever the script runs, you could make the socket an outside variable. This will make working with it a bit easier (since you don't have to deal with the refs or stale closures etc), but also makes the component less reusable. That may or may not be an issue for you. You could have the socket passed down as a prop, but that'll just kick the issue up the call chain, with the same problems and solutions.
I accepted your answer but had another question: if in the code I submitted I was creating a new websocket on each render, was the previous one closing on its own? Is it bad practice to do it that way? If on form submission I am creating new subscriptions it might be easier to just make a new websocket each time with the subscriptions instead of unsubscribing
I think, once nothing else can reference the socket, it will eventually be garbage collected, and when that occurs, the browser will close the socket. If you did create sockets multiple times for a good reason and then just stopped referencing them for GC, I think that'd be just fine. For this particular situation, I'm not sure on the context - I'd do whatever makes the code easier to write and understand.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.