3

i've been having some issues with what im guessing is closure and i am only guessing because it looks like i don't understand it as well as i thought. Here is the issue:

I am trying to establish a web socket connection using Stomp. The way it works is, you create a stomp client

let stompClient = Stomp.over(socket);

and then you subscribe to some routes, passing in the route and passing in a callback that gets called when that route is hit

stompClient.subscribe("/topic/some-route",(message) => myCallback(message))

Since creating a client seems like a one time thing, i decided to put it in a useEffect hook with no dependencies in the array, so that the client only gets created when the component mounts.

useEffect(() => {
//some unimportant setup

let stompClient = Stomp.over(socket);

stompClient.subscribe("/topic/some-route",(message) => myCallback(message))

}, [])

I also keep some state in my component, to keep it simple, lets say i keep this:

const [counter, setCounter] = useState(0);

And what i want my callback to do, is just print out the state, so:

const myCallback = (message /* the message variable is unimportant here */ ) => {
console.log(counter);
}

Now, as the user is using the app, the counter state is going to change, but whenever the callback gets triggered, it always prints 0. Using the knowledge of closure i have, i am guessing that...: (this is question #1)

  1. Because the function closure (the word context seems more fitting for me) gets initialised when the function is created, the function takes the counter variable as a 0, and that is the reason it always prints 0. Is this statement correct?

If i move the stompClient.subscribe("/topic/some-route",(message) => myCallback(message)) outside of the useEffect hook, the myCallback function will print out the current state, however, considering that this .subscribe(...) function creates a whole new subscription towards my stomp broker, i would end up with hundreds of subscriptions, one for each state update i have, which is obviously not a solution. Which leads me to question #2,

  1. Considering i need to call this .subscribe(message, callback) function only once, how can i pass in a callback that will reflect my state?

Thank you!

Emperor
  • 335
  • 3
  • 11

2 Answers2

0

Have you tried calling a function from your callback and then logging the state from that function ?

const myCallBack = (message) => {
  onMessageReceived();
}

const onMessageReceived = () => {
  console.log(counter);
}

Maybe?

Brian Patterson
  • 1,615
  • 2
  • 15
  • 31
  • The code you wrote is exactly the code i have and does not work. It looks like when the callback is created it points towards count:0, and even though count gets updated during the second useEffect many times, the callback still prints count: 0 – Emperor May 10 '20 at 17:23
  • The point was to use useEffect twice due to wanting to only run it once. Did you even read my answer? How is the code exactly the same even though it is different? You didn't include your full code, so I'm just guessing based off the fragments you provided. Try to keep in mind, I'm attempting to help you here, so try to put in a little effort on your end as well. – Brian Patterson May 10 '20 at 17:41
  • My apologies. As for the first point you made, yes, we cannot change the state of counter from within the useEffect hook that runs only on component mount, and to if i should be getting the correct value even if i change it from the outside, i would have thought i should, but obviously i do not get the correct value. As for what i mean that this is exactly the code i have, the first useEffect is as its stated in the question, and i stated that as the user is using the app the counter variable will change. Using another useEffect with a `counter` in the dependency array is how i do it. – Emperor May 10 '20 at 17:58
  • OK, I gotcha, sorry I misunderstood how you meant that. Again, posting your full code would have avoided that. If you can provide the full component code it would be helpful. I understand that is not always possible though. So you are modifying the count by calling setCount or w/e and your callback is still logging 0, is that correct? Also, have you tried removing the dependency array from useEffect, what happens then? – Brian Patterson May 10 '20 at 18:09
  • So if the callback is executed after subscribe finishes, and subscribe is only run once when mounted (when count is 0) then you shouldn't be getting any other value back than 0 because the callback is only running once, right ? And no matter how many times you update state and re-render, that callback doesn't get called again. Right? – Brian Patterson May 10 '20 at 18:14
  • The callback is not executed after subscribe finishes. The subscribe method "registers" the callback, so that when a message is sent to "/topic/some-route", the callback will be executed. So, think of it like this, the app renders, then after some time a message is sent to /topic/some-route. By that time, thorugh user interaction with the app, the count state should be some number like 146. But when the call back DOES execute, it prints out 0, the state that the callback method was initlized to upon first render. – Emperor May 10 '20 at 19:18
  • As for your other comment, i am setting count by using setCount (it gets set, that part works properly), and i have tried removing the dependency array, in fact i have moved it out of the useEffect as well. – Emperor May 10 '20 at 19:18
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/213572/discussion-between-brian-patterson-and-emperor). – Brian Patterson May 10 '20 at 19:33
0

In case somebody will face the same issue. This can be resolved with useRef() hook.

let stompClient = Stomp.over(socket);

const [counter, setCounter] = useState(0);
const stateRef = useRef();

// make stateRef always have the current count
// your "fixed" callbacks can refer to this object whenever
// they need the current value.  Note: the callbacks will not
// be reactive - they will not re-run the instant state changes,
// but they *will* see the current value whenever they do run
stateRef.current = count;

useEffect(() => {
   let stompClient = Stomp.over(socket);
   stompClient.subscribe("/topic/some-route",(message) => myCallback(message))
}, [])

const myCallback = (msg) => {
   console.log(stateRef.counter);
}

The answer was found here

Dmitrii
  • 1
  • 1