Masaki Minamide
Masaki Minamide

Reputation: 79

How do I execute function after loading all external library with React Hook useEffect?

I want to execute main() after making sure loading all libraries.

useEffect(() => { 
    for (const id in urls) {
      let tag = document.createElement('script');
      tag.async = false;
      tag.src = urls[id];
      let body = document.getElementsByTagName('body')[0];
      body.appendChild(tag);
    }
    main()
},[]); 

Upvotes: 3

Views: 3275

Answers (1)

Ori Drori
Ori Drori

Reputation: 192252

Create a loadScript() function that returns a promise. The promise is resolved via the load event handler:

const loadScript = url => new Promise(resolve => {
  const tag = document.createElement('script');
  tag.async = false;
  tag.src = url;
  const body = document.body;
  body.appendChild(tag);
  tag.addEventListener('load', resolve, {
    once: true
  });
});

Now you can map the urls to promises by loading the script, and use Promise.all() to wait for the loading of the entire batch:

useEffect(() => {
  Promise.all(Object.values(urls).map(loadScript))
    .then(main);
}, [urls]);

You can create a custom useScripts hook to encapsulate this functionality:

const { useEffect, useState } = React;

const loadScript = (url, target) => new Promise(resolve => {
  const tag = document.createElement('script');
  tag.async = false;
  tag.src = url;
  target.appendChild(tag);
  tag.addEventListener('load', resolve, {
    once: true
  });
});

const useScripts = (urls, cb = () => {}, deps) => {
  useEffect(() => {
    const body = document.body;
  
    Promise.all(urls.map(url => loadScript(url, body)))
      .then(cb);
  }, deps);
}

const Demo = ({ urls }) => {
  const [loading, setLoading] = useState(true);
  
  useScripts(Object.values(urls), () => setLoading(false), [urls, setLoading]);
  
  return loading ? 'Loading' : 'done';
};

const urls = { lodash: 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js', moment: 'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.js' };

ReactDOM.render(
  <Demo urls={urls} />,
  root
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

Upvotes: 1

Related Questions