Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
import { useState, useEffect } from 'react';
// Usage
function App() {
const [loaded, error] = useScript(
'https://pm28k14qlj.codesandbox.io/test-external-script.js'
);
return (
<div>
<div>
Script loaded: <b>{loaded.toString()}</b>
</div>
{loaded && !error && (
<div>
Script function call response: <b>{TEST_SCRIPT.start()}</b>
</div>
)}
</div>
);
}
// Hook
let cachedScripts = [];
function useScript(src) {
// Keeping track of script loaded and error state
const [state, setState] = useState({
loaded: false,
error: false
});
useEffect(
() => {
// If cachedScripts array already includes src that means another instance ...
// ... of this hook already loaded this script, so no need to load again.
if (cachedScripts.includes(src)) {
setState({
loaded: true,
error: false
});
} else {
cachedScripts.push(src);
// Create script
let script = document.createElement('script');
script.src = src;
script.async = true;
// Script event listener callbacks for load and error
const onScriptLoad = () => {
setState({
loaded: true,
error: false
});
};
const onScriptError = () => {
// Remove from cachedScripts we can try loading again
const index = cachedScripts.indexOf(src);
if (index >= 0) cachedScripts.splice(index, 1);
script.remove();
setState({
loaded: true,
error: true
});
};
script.addEventListener('load', onScriptLoad);
script.addEventListener('error', onScriptError);
// Add script to document body
document.body.appendChild(script);
// Remove event listeners on cleanup
return () => {
script.removeEventListener('load', onScriptLoad);
script.removeEventListener('error', onScriptError);
};
}
},
[src] // Only re-run effect if script src changes
);
return [state.loaded, state.error];
}
@gokulkrishh

This comment has been minimized.

Copy link

commented Nov 16, 2018

In line no 58, loaded should be false ? if there is a error while loading.

@gragland

This comment has been minimized.

Copy link
Owner Author

commented Nov 16, 2018

@gokulkrishh That just indicates that the script is no longer loading, but I can see how that's a bit confusing. Will update if I think of a better way to do it, but open to suggestions :)

@tikotzky

This comment has been minimized.

Copy link

commented Nov 18, 2018

It seems that if the script is cached then state doesn’t get updated and loading will stay false.

@gragland

This comment has been minimized.

Copy link
Owner Author

commented Nov 18, 2018

@tikotzky Woops, good catch! Updated the code with a fix.

@mohammedzamakhan

This comment has been minimized.

Copy link

commented Nov 19, 2018

it can just return state, instead of [state.loaded, state.error], considering https://twitter.com/_developit/status/1057636803354648582

@stunaz

This comment has been minimized.

Copy link

commented Jan 19, 2019

concurrency issue

@webOS101

This comment has been minimized.

Copy link

commented May 4, 2019

Seems like the script status should be stored in the cache so that it doesn't get marked as loaded if the same script is attempted during loading.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.