Paz Lazar
Paz Lazar

Reputation: 239

React hooks change the state on postMessage

I have a component with some state using useState.

This component opens a new tab and should receive a message event on postMessage. For some reason, after I get the event, the state returns to its initial state.

I tried saving the state to localStorage and it worked.

index.js

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

const App = () => {
  const [state, setState] = useState("hello");

  const onClick = () => {
    setState("world");
    window.open("tab.html");
  };

  const onMessageReceivedFromIframe = event => {
    console.log("onMessageReceivedFromIframe", state, event);
  };

  const addIframeListener = () =>
    window.addEventListener("message", onMessageReceivedFromIframe);
  const removeIframeListener = () =>
    window.removeEventListener("message", onMessageReceivedFromIframe);

  useEffect(() => {
    addIframeListener();
    return () => {
      removeIframeListener();
    };
  }, []);

  useEffect(() => {
    console.log("Current state: ", state);
  }, [state]);

  return <button onClick={onClick}>Click here</button>;
};

ReactDOM.render(<App />, document.getElementById("app"));

tab.html

<html>
  <body>
    tab
    <script>
      setTimeout(() => {
      console.log("post");
      window.opener.postMessage("some message", "https://9jzj7.csb.app");
      window.close();
      }, 2000);
    </script>
  </body>
</html>

The state returns to its initial state.

A demo can be seen here: https://codesandbox.io/s/trusting-waterfall-9jzj7

BTW: I know that the event is not fired from tab.html but there is still an event that fires on tab opening.

Upvotes: 11

Views: 9957

Answers (2)

intekhab
intekhab

Reputation: 1596

The problem is with the below code

useEffect(() => {
    addIframeListener();
    return () => {
      removeIframeListener();
    };
  }, []);

Since its calling addIframeListener and removeIframeListener from inside effect and the state state is getting used inside it.

Change the code as

useEffect(() => {
    addIframeListener();
    return () => {
      removeIframeListener();
    };
  }, [state]);

If there is any props getting used by framelistener function, keep that in dependent array otherwise you will end up getting old props

Upvotes: 1

Prithwee Das
Prithwee Das

Reputation: 5226

seems your problem was with the deps array of your effect hook, and onMessageReceivedFromIframe being created on every render. the solution below should work like you expect. here is a link to the codesandbox

const App = () => {
  const [state, setState] = useState("hello");

  const onClick = () => {
    setState("world");
    console.log(state);
    window.open("tab.html");
  };

  const onMessageReceivedFromIframe = React.useCallback(
    event => {
      console.log("onMessageReceivedFromIframe", state, event);
    },
    [state]
  );

  useEffect(() => {
    window.addEventListener("message", onMessageReceivedFromIframe);
    return () =>
      window.removeEventListener("message", onMessageReceivedFromIframe);
  }, [onMessageReceivedFromIframe]);

  React.useEffect(() => {
    console.log("state", state);
  }, [state]);

  return <button onClick={onClick}>Click here</button>;
};

Upvotes: 5

Related Questions