Search code examples
javascriptiframegatsby

(Gatsby) Datawrapper iframes aren't displaying before I've refreshed the page


I'm using Datawrapper to display graphs as iframes on my website.

I just recently migrated from Remark to MDX. With the new setup, my Datawrapper graphs aren't loading before the page that contains the graphs is allowed to load in the "traditional" sense (i.e., hitting refresh or following the page that contains the graphs directly).

So, if you follow this link, the graphs display fine: kolstadmagnus.no/ekspert-mener-a-ha-svaret-bak-krfs-stortingssmell.

But if you go to the home page (kolstadmagnus.no) and then hit the link (second-top post), the graphs won't display.


Some context

Datawrapper iframes use this script so that the iframes will be responsive:

!function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}();

Since Gatsby works the way it does (not loading every page "traditionally"—sorry, I don't know what the feature is called), I have had to put this script in the html.js file, like this:

<body {...props.bodyAttributes}>
  {props.preBodyComponents}
  <div
    key={`body`}
    id="___gatsby"
    dangerouslySetInnerHTML={{ __html: props.body }}
  />
  {props.postBodyComponents}
  <script
    dangerouslySetInnerHTML={{
      __html: `
            !function(){"use strict";window.addEventListener("message",(function(e){if(void 0!==e.data["datawrapper-height"]){var t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}(); `
    }}
  />
</body>;

This causes the script to be executed for the entire website instead of just for that single page, thereby solving the issue where hitting refresh would be required after entering the pages with the iframes.


With MDX though, problems are back. But now the iframes don't display at all.

How do I solve this?


Solution

  • Gatsby extends its navigation and its "loading page" from @reach/router, so from React.

    I'd extremely discourage embedding a custom script that needs to be rendered in certain pages in the html.js since, as you pointed, it will be loaded globally. You can achieve the same effect by running it locally or atomizing it.

    The problem you are facing is that the script points directly to the DOM (because of the use of window.addEventListener) while in React (and hence with Gatsby) you create and manipulate a virtual DOM (vDOM). Playing and manipulating both can cause misleading behaviors because one doesn't know when the other changes and vice-versa, breaking the hydration and rehydration process (that's why you see the results on refresh: because hydration never occurs).

    That said the ideal solution would be using a React-based Datawrapper module like react-datawrapper-chart or using another custom solution. Making scripts responsive is not a big deal, you can make the same calculations using React-based code.

    Alternatively, following your "working" approach, you can load your custom script using the Helmet component on a certain page:

    const SomeSpecificPage = () =>{
    
    return <>
       <Helmet>
        <script
           dangerouslySetInnerHTML={{
             __html: `
                   !function(){"use strict";window.addEventListener("message", 
      (function(e){if(void 0!==e.data["datawrapper-height"]){var 
    t=document.querySelectorAll("iframe");for(var a in e.data["datawrapper-height"])for(var r=0;r<t.length;r++){if(t[r].contentWindow===e.source)t[r].style.height=e.data["datawrapper-height"][a]+"px"}}}))}(); `
        }}
      />
       </Helmet>
    </>
    
    }