How to keep iframe mounted in React after it's parent unmounts

52 Views Asked by At

I have a react application that has components containing iframe. My components do get mounted and unmounted based on a state (e.g. {(showApp === true) && <MyApp/>}). Every time my component gets mounted, my iframe gets loaded again which do make some additional network calls and results in increased time. Is there a way I can memoize my components that can prevent iframe from loading again?

Here is a minimal reproducible example of my problem:

const App1 = () => {
  useEffect(() => {
    console.log("App1 mounted");
  }, []);

  return (
    <iframe
      width="100%"
      height="254"
      src="https://www.google.com/webhp?igu=1"
    ></iframe>
  );
};

const App2 = () => {
  useEffect(() => {
    console.log("App2 mounted");
  }, []);

  return (<div style={{ color: "red" }}>This is App2</div>);
};

const appMap = {
  1: <App1 />,
  2: <App2 />,
};

const MyApp = ({ app }) => {
  const Application = useMemo(() => appMap[app], [app]);

  return (
    <div>
      <span>This is MyApp Container:</span>
      {Application}
    </div>
  );
};

export default function App() {
  const [showApp, setShowApp] = useState(null);

  const App = useMemo(() => <MyApp app={showApp} />, [showApp]);

  return (
    <div>
      <div onClick={() => setShowApp("1")}>Show App 1</div>
      <div onClick={() => setShowApp("2")}>Show App 2</div>
      {showApp && App}
    </div>
  );
}
1

There are 1 best solutions below

0
Adam Jenkins On BEST ANSWER

There’s no “memoization” involved. *What you want to do is to never unmount the component containing the iframe (or the iframe itself), you just want to hide it

useMemo is way overused, and it’s not applicable here anyway

You have 2 potential solutions:

  1. Don't unmount the mounted component that renders the iframe - hide it instead

  2. Use native dom methods to append the iframe when the component "containing it" is mounted and toggle the iframe's visibility when the component "containing it" is mounted/unmounted (so it's always in the DOM, just not always visible).

Your current code structure lends itself towards solution 2. This is a rough example with comments to help illustrate the whats and whys. Your precise needs will likely be somewhat different:

const IFRAME_ID = "MY_IFRAME_ID"
const getIframe = () => document.getElementById(IFRAME_ID)
const createIframe = (attrs) => {
   const iframe = document.createElement('iframe')
   iframe.id = IFRAME_ID;
   iframe.width = '100%';
   iframe.height = '254';
   iframe.src = 'https://www.google.com/webhp?igu=1'
   return iframe;
}

const App1 = () => {
  useEffect(() => {
     let iframe = getIframe()
     if(iframe) {
        // when this component mounts and the iframe exists, make sure it's visible
        iframe.style.display = 'block';
     } else {
        // when this component mounts and the iframe doesn't yet exist
        // create it and append it to the DOM
        iframe = createIframe(); 
        document.body.appendChild(iframe);
     }

     return () => {
        // when this component unmounts, hide the iframe
        iframe.style.display = 'none';
     }
  }, []);

  return null
};