Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scripts are not loaded synchronously within the same Helmet component #419

Open
darkowic opened this issue Dec 6, 2018 · 16 comments
Open
Assignees
Labels

Comments

@darkowic
Copy link

darkowic commented Dec 6, 2018

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

@tmbtech tmbtech self-assigned this Dec 7, 2018
@tmbtech tmbtech added the bug label Dec 7, 2018
@tmbtech
Copy link
Contributor

tmbtech commented Dec 7, 2018

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.

@darkowic
Copy link
Author

darkowic commented Dec 7, 2018

@tmbtech definitely :) I was looking for it through the issues and did not found it

@tmbtech
Copy link
Contributor

tmbtech commented Dec 7, 2018

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.

@jamelait
Copy link

Any workaround?

@Zenoo
Copy link

Zenoo commented Sep 17, 2019

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

@BigSully
Copy link

BigSully commented Oct 20, 2019

Still have this issue. I have a bunch of <scripts> in <Helmet>, but they seems to not execute in the order they are declared. My solution is that I manually downloaded all scripts and concat them into one script in the order they are declared in the original page.

@zub2
Copy link

zub2 commented May 27, 2020

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).

@raffazizzi
Copy link

@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

@KudMath
Copy link

KudMath commented Nov 10, 2020

@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

did you pass async={false} to every script or to your Helmet HOC ?

@cseas
Copy link

cseas commented Dec 16, 2020

@jakezien
Copy link

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.

@WangNingning1994
Copy link

Super annoying

@yanickrochon
Copy link

Just found this. The onLoad attribute is also not set properly.

const handleScriptLoaded = () => { console.log("**** LOADED"); };
<Helmet>
   <script src="https://domain.com/path/to/script.js" onLoad={handleScriptLoaded} type="text/javascript" />
</Helmet>

Creates the element:

<script src="http://app.thermoform.localhost/auth/js/api.js" onload="() => {
    console.log(&quot;*** LOADED&quot;);
  }" type="text/javascript" data-rh="true"></script>

This is completely unusable.

@vergieet
Copy link

just another workaround

i'm giving my script setTimeout with 50ms, and it's worked fine and still acceptable on my side

@jcubic
Copy link

jcubic commented Feb 10, 2024

There was no workaround so this is what I used:

function getScript(script: string) {
    return new Promise((resolve, reject) => {
        const $script = document.createElement('script');
        $script.onload = resolve;
        $script.onerror = reject;
        $script.src = script;
        document.head.appendChild($script);
    });
}

function useScripts(user_scripts: string[]) {
    useLayoutEffect(() => {
        const scripts = [...user_scripts];
        (function loop() {
            if (scripts.length) {
                const script = scripts.shift();
                getScript(script).then(loop);
            }
        })();
    }, []);
}

@ivan-robert
Copy link

You can add a listener in the Helmet and use react states to check if the first script is loaded before executing the second . To do so use the onChangeClientState Helmet prop

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests