useScript

Some third-party code still arrives as a classic <script> tag: a payment widget, a maps SDK, an analytics snippet. Loading it from index.html makes every visitor pay for it. A Hook loads it from the component that needs it and reports when it is usable.

Loading a third-party script on demandJavaScript
import { useEffect, useState } from 'react';
export function useScript(src) {
  const [status, setStatus] = useState('loading');
  useEffect(() => {
    const found = document.querySelector(`script[src="${src}"]`);
    if (found) setStatus(found.dataset.status || 'loading');
    const el = found || document.body.appendChild(
      Object.assign(document.createElement('script'), { src, async: true }));
    const done = e => {
      el.dataset.status = e.type === 'load' ? 'ready' : 'error';
      setStatus(el.dataset.status);
    };
    const types = ['load', 'error'];
    types.forEach(t => el.addEventListener(t, done));
    return () => types.forEach(t => el.removeEventListener(t, done));
  }, [src]);
  return status;
}

Callers branch on the status: const status = useScript(SDK_URL);, render a spinner while it is 'loading', and touch window.PaymentSDK only once it is 'ready'.

Note what the cleanup does not do. Removing the <script> element would undo nothing the script already did — globals, listeners and timers survive — while the next mount would download and execute it again. So the tag stays, the listeners go, and data-status lets a later mount skip straight to the final state.

For code you control, await import('./chart.js') is better in every way: a module, typed, splittable by the bundler. And in React 19 7,897 you can render <script async src={url} /> inside the component that needs it; React hoists it and loads it once even when several components render the same tag.