react-helmet: Scripts are not loaded synchronously within the same Helmet component

I would like to inject 2 script tags like this: jQuery is just an example

      <Helmet>
        <script
          src="https://code.jquery.com/jquery-3.3.1.min.js"
          integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
          crossorigin="anonymous"
        ></script>
        <script>
          {`
            console.log('Test', typeof $);
          `}
        </script>
      </Helmet>

After the first script is loaded I want to execute the second inline script. Normally, when you put 2 scripts into HTML without any async or defer they will load synchronously.

Currently both scripts will be executed asynchronously. See reproduction here - https://codesandbox.io/s/l9qmrwxqzq

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 19
  • Comments: 16 (2 by maintainers)

Most upvoted comments

Super annoying

Super annoying that this is still a thing and that it’s not documented in any obvious way. Took me way too long to figure out what was going on with my scripts. The readme says “Helmet takes plain HTML tags and outputs plain HTML tags. It’s dead simple, and React beginner friendly.” But plain HTML script tags load synchronously, and Helmet changes this behavior on the sly; that’s not simple or beginner friendly.

Still having the same issue here. Is there any way to fix it?

Edit: Since I didn’t find any workaround, I created one myself, with the package react-append-head

In the meantime, internally we do something similar to this , maybe it’ll help.


class LoadExternalScript extends React.Component {
    static defaultProps = {
        onLoad: () => {},
        onError: () => {},
    };

    render() {
        const {props} = this;
        if (!canUseDOM) {
            props.onError("DOM not found");
            return false;
        }

        if (!document.getElementById(props.id)) {
            const script = document.createElement("script");
            script.src = props.src;
            script.id = props.id;
            script.onload = props.onLoad;
            script.onerror = props.onError;

            if (document.body) {
                document.body.appendChild(script);
            }
        } else {
            props.onError("script already loaded");
        }

        return false;
    }
}


// some other file

class Foobar extends React.Component {
  state = {
      status: "pending"
  } 
  render() {
     return (
         <LoadExternalScript 
              src="example.com" 
              id="example" 
              onLoad={() => this.setState({status: "scriptLoaded"})}
         />
     )
  }
}

<Foobar /> can then have additional checks on if needs to load a script again and what components it needs to render after the global js script has loaded.

or depending on your case, you might just want to load the script in the html template? idk just throwing out ideas.

goodluck.

@zub2 setting async={false} as you suggested just worked for me! I’m using Helmet 6.1.0 within Gatsby 2.23.3 with TypeScript

Unfortunately this is a known issue. We need to do a better job at communicating this bug to the everyone.

Note to self add a FAQ to the readme related to this bug.

It seems that the script elements created by helmet have the async attribute and AFAIK there is no way how to disable it (e.g. by passing async={false} in JSX - this just sets the value of async to "false" but for unsetting it, the attribute would have to be removed).