SvenKirschbaum / react-stomp-hooks

This repository contain a react library which enables simple access to stomp subscriptions via hooks.
MIT License
63 stars 13 forks source link

this._stompHandler is undefined when using stomp client on initial load of child component #16

Closed ad044 closed 2 years ago

ad044 commented 2 years ago

Hello! Thank you for making this library, made working with stompjs in React much easier.

I have one issue though - I need to use stompClient in a child component when it first loads. Example code:

const Child = () => {
  // ...
  const stompClient = useStompClient();

  useEffect(() => {
    if (stompClient) {
      // use stomp client to send a message
    }
  }, [stompClient]);

  // ...
};

const App = () => {
  return (
    <StompSessionProvider
      url={"ws://localhost:8080/ws"}
      debug={(str) => console.log(str)}
    >
      <BrowserRouter>
        <Routes>
          <Route path="child/:uri" element={<Child  />} />
        </Routes>
      </BrowserRouter>
    </StompSessionProvider>
  );
};

Doing this throws a Uncaught TypeError: this._stompHandler is undefined error, and I'm not sure how to deal with it. It appears it is caused by the stompClient existing, but stompClient.connected not being true.

Adding stompClient?.connected inside the dependency array of useEffect and checking for if (stompClient && stompClient.connected) prevents the error, but does not seem to make it re-fire automatically (it only re-fires when I interact with the page).

I also tried appending const [connected, setConnected] to the App component, and adding a onConnect={() => setConnected(true)} property to the StompSessionProvider and using that:

const App = () => {
  const [connected, setConnected] = useState(false);
  return (
    <StompSessionProvider
      url={"ws://localhost:8080/ws"}
      debug={(str) => console.log(str)}
      onConnect={() => setConnected(true)}
    >
      <BrowserRouter>
        <Routes>
          <Route path="child/:uri" element={<Child  />} />
        </Routes>
      </BrowserRouter>
    </StompSessionProvider>
  );
};

Either passing connected down as prop to the child and checking for it inside useEffect, or waiting for it to be true before rendering the BrowserRouter. The results appear to be the exact same.

I'm not sure if I'm doing something dumb here :(

Edit: Forgot to mention, one thing to keep in mind is that if I render App and afterwards navigate to Child by some means (pressing a button or similar) the error isn't thrown. It only seems to happen when I directly load the child route via pasting it in the url bar inside the browser or I press refresh while on it. I assume this works in the first case because the parent has time to connect properly before navigating elsewhere.

SvenKirschbaum commented 2 years ago

Hello!

This is a usecase I ran into myself some time ago. The pattern you describe is potentially problematic, because it can cause issues when the stomp connection is lost and subsequently gets reestablished. Depending on your logic you would have to either resend the initial message again, or resending could break something unintentionally. In general I think a subscription is most of the time a better option.

Anyway, that is of cause not a reason for preventing you from doing this if you want to. I will publish a new version v2.1.0 shortly, that will delay exposing the stomp client until the connection is established, and setting it back to undefined if the connection is lost. Specifing the client as a dependency of your effect and checking if it is undefined inside, like you tried already above, will then be sufficient to reach your desired effect.