Handling of iframes in React

How to prevent iframe content rerendering and organize message exchange via postMessage

iframe is a very convenient tool if you need to decouple some content from the main content or load an external content. The new html5 specification offers some new usefull options, like allowfullscreen or scrolling. In REMROB project iframes are used to create widgets.

Porting pages from krakenjs to reactjs there were some difficulties, the iframe implementation had to be moved from classic templates to components rerendered by each state update. And this is a completely different approach.

There was no applicable example or generally useful information about the interaction of react and iframes for my scenario: completely decoupled iframe (with its content) without rerendering, but maintaining permanent communication via postMessage functionality. After analysing the available docs and examples I ended up with following solution.

  1. Step: Disable rerendering
    According do React Docs: “If you know that in some situations your component doesn’t need to update, you can return false from shouldComponentUpdate instead, to skip the whole rendering process, including calling render() on this component and below.” The code then looks like:
    shouldComponentUpdate() {
     return false;
    }

    The above expression guarantees that the iframe will be not reloaded.
  2. Step: Detection of State Update & Sending Data to iframe
    Don’t make a mistake by using componentDidUpdate. Or you will run into the danger to rerender the React component each time the props update and eventually reload the iframe content. But if you took the previous step, this wouldn’t work anyway, because after returning false in shouldComponentUpdate componentDidUpdate is disabled (see here). Instead you should use componentWillReceiveProps.

    componentWillReceiveProps(nextProps) {
     if (this.props(old) !== nextProps(new) {
     send message...
     }
     }

    Using the above method you will get the old and the new props and will be able to compare them and send only the changed values to iframe.
  3. Step: Sending data to iframe
    To transfer the data to the iframe the following expression is used in static template pages:
    document.getElementById("iframe").contentWindow.postMessage(data, "*");
    This is however not the recomended way for reactjs. Instead ref attribute with callback should be used to access the iframe element and its content.
    render(){
     return (
     <iframe sandbox="allow-scripts" ref={(f) => this.ifr = f; } />
     )
    }

    Then the iframe element is available in the context object “this”. You can access it the following way:
    componentDidMount(){
     this.ifr.onload = () => {
     this.ifr.contentWindow.postMessage('hello', "*");
     }
    }

    The method componentDidMount() guarantees that the iframe element is mounted and available to access. According to Srigi, to ensure that the iframe content is loaded as well and ready to process the incoming messages, the postMessage can be wrapped into onload event. Then you should be able to send data to iframe.
  4. Step: Last but not least - Listening data from iframe
    For this purpose an event handler has to be bind to the root window object. This event handler should also be unbind, if you leave the page. It is important to access the same function. This can be handled by defining an external function and calling it in componentDidMount and componentWillUnmount

    componentDidMount() {
     window.addEventListener("message", this.handleFrameTasks);
     }
     componentWillUnmount() {
     window.removeEventListener("message", this.handleFrameTasks);
     }
     handleFrameTasks = (e) => {
     if(e.data.from.iframe === "load_products") then...
     }

A summarized React class you can find on the gist. I hope this example can help someone to get faster to a desired solution. If you find a bug, have questions or would like to suggest an improvement feel free to contact me.

Update 16.12.2016:
Sometimes it can happen that the state changed before componentDidMount and the iframe is still not available. A error may occur: “iframe is not defined”. Therefore a small check if(this.ifr) before passing a message to iframe can help to avoid this error. The data will stay in the store and can be sent after componentDidMount.

Show your support

Clapping shows how much you appreciated Emir Bakhtarov’s story.