DOM

useScript

Load an external script and report its load status.

Demo

status
idle
window.confetti
undefined

The <script> tag mounts only when you click — nothing loads on page open.

Usage

use-script.tsxtsx
import { useScript } from "hookli";

export function Demo() {
  const status = useScript("https://example.com/widget.js");

  if (status === "ready") return <p>Widget loaded.</p>;
  return <p>Loading… ({status})</p>;
}

API

useScript.d.tsts
useScript(src: string | null, options?: UseScriptOptions): UseScriptStatus

Parameters

NameTypeDefaultDescription
srcstring | nullThe script URL to load, or null to skip loading and stay idle.
optionsUseScriptOptions{}Optional shouldPreventLoad and removeOnUnmount flags.

Returns

NameTypeDescription
statusUseScriptStatusThe current load status: "idle" | "loading" | "ready" | "error".

UseScriptStatus

NameTypeDescription
value"idle" | "loading" | "ready" | "error"Lifecycle of the injected script tag.

UseScriptOptions

Flags controlling when the script loads and unloads.

NameTypeDescription
shouldPreventLoadbooleanKeep the status "idle" without injecting the script — useful for deferring a load.
removeOnUnmountbooleanRemove the injected <script> tag when the component unmounts.

Hook

The implementation, straight from hookli. Copy it, or install the package.

src/hooks/use-script/use-script.tstsx
import { useEffect, useState } from "react";

type UseScriptStatus = "idle" | "loading" | "ready" | "error";

interface UseScriptOptions {
  shouldPreventLoad?: boolean;
  removeOnUnmount?: boolean;
}

export function useScript(
  src: string | null,
  options?: UseScriptOptions,
): UseScriptStatus {
  const [status, setStatus] = useState<UseScriptStatus>(() => {
    if (!src || options?.shouldPreventLoad) return "idle";
    return "loading";
  });

  useEffect(() => {
    if (!src || options?.shouldPreventLoad) {
      setStatus("idle");
      return;
    }

    let script = document.querySelector<HTMLScriptElement>(
      `script[src="${src}"]`,
    );

    if (script === null) {
      script = document.createElement("script");
      script.src = src;
      script.async = true;
      script.setAttribute("data-status", "loading");
      document.body.appendChild(script);

      const setAttributeFromEvent = (event: Event) => {
        script?.setAttribute(
          "data-status",
          event.type === "load" ? "ready" : "error",
        );
      };

      script.addEventListener("load", setAttributeFromEvent);
      script.addEventListener("error", setAttributeFromEvent);
    } else {
      setStatus(
        (script.getAttribute("data-status") as UseScriptStatus) ?? "loading",
      );
    }

    const setStateFromEvent = (event: Event) => {
      setStatus(event.type === "load" ? "ready" : "error");
    };

    script.addEventListener("load", setStateFromEvent);
    script.addEventListener("error", setStateFromEvent);

    return () => {
      if (script) {
        script.removeEventListener("load", setStateFromEvent);
        script.removeEventListener("error", setStateFromEvent);
        if (options?.removeOnUnmount) {
          script.remove();
        }
      }
    };
  }, [src, options?.shouldPreventLoad, options?.removeOnUnmount]);

  return status;
}

View source on GitHub

Related hooks