useScriptLoading saved progress…

useScript

Third-party widgets — Stripe, a maps SDK, an analytics snippet — ship as a <script src> you're supposed to drop in the page <head>. In a SPA you often want to load them lazily, only on the route that needs them, and know when they're ready so you can safely touch their globals. useScript does that: give it a URL, it injects the tag once and returns a status you can render against.

Implement useScript(src). It returns 'idle' | 'loading' | 'ready' | 'error'. It appends the <script> to the body, tracks its load/error events, and — crucially — dedupes: if the same src is already on the page, it reuses that tag instead of injecting a duplicate.

Signature

function useScript(src) {
  // returns 'idle' | 'loading' | 'ready' | 'error'
}

Examples

function Checkout() {
  const status = useScript('https://js.stripe.com/v3');
  if (status === 'ready') return <StripeForm />;
  if (status === 'error') return <p>Failed to load payments.</p>;
  return <Spinner />;
}
// Two components both call useScript(sameSrc) — only ONE <script> is injected.
const a = useScript('/maps.js'); // injects
const b = useScript('/maps.js'); // reuses, reports the same status

Notes

  • Inject once, into the DOM — create a <script>, set src and async, append to document.body.
  • Track load/error — attach listeners that flip status to ready/error; start at loading (or idle when src is falsy).
  • Dedupe by src — query for an existing script[src="…"]; if found, don't inject again — read its current status from a data-status attribute you keep updated.
  • Clean up — remove the listeners this hook added on unmount or when src changes (leave the tag so other consumers keep working).
Loading editor…