Skip to content

Commit dc258dd

Browse files
committed
fix(useScript): support attributes on useScript
1 parent 839c9e4 commit dc258dd

File tree

2 files changed

+46
-2
lines changed

2 files changed

+46
-2
lines changed

src/__tests__/useScript.test.tsx

+23
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,26 @@ test("idle until url", async () => {
6666

6767
await vi.waitUntil(() => result.current === "ready");
6868
});
69+
70+
test("should be able to add custom attributes to the script", async () => {
71+
const { rerender } = renderHook(() =>
72+
useScript("/test-script.js", {
73+
attributes: {
74+
id: "test-id",
75+
"data-test": "true",
76+
nonce: "test-nonce",
77+
},
78+
}),
79+
);
80+
81+
const script = document.querySelector("script[src='/test-script.js']");
82+
if (script) {
83+
expect(script).toHaveAttribute("id", "test-id");
84+
expect(script).toHaveAttribute("data-test", "true");
85+
expect(script).toHaveAttribute("nonce", "test-nonce");
86+
}
87+
88+
rerender();
89+
rerender();
90+
rerender();
91+
});

src/hooks/useScript.ts

+23-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,22 @@ import { useEffect, useState } from "react";
22

33
type ScriptStatus = "idle" | "loading" | "ready" | "error";
44

5+
type ScriptOptions = {
6+
attributes?: Record<string, string>;
7+
};
8+
59
/**
610
* Hook to load an external script. Returns true once the script has finished loading.
711
*
812
* @param url {string} The external script to load
13+
* @param options {ScriptOptions} The options for the script
14+
* @param options.attributes {HTMLScriptElement["attributes"]} Extra attributes to add to the script element
915
* @returns {ScriptStatus} The status of the script
1016
* */
11-
export function useScript(url?: string): ScriptStatus {
17+
export function useScript(
18+
url: string | undefined,
19+
options?: ScriptOptions,
20+
): ScriptStatus {
1221
const [status, setStatus] = useState<ScriptStatus>(() => {
1322
if (!url) return "idle";
1423
if (typeof window === "undefined") return "loading";
@@ -20,6 +29,9 @@ export function useScript(url?: string): ScriptStatus {
2029
return (script?.getAttribute("data-status") as ScriptStatus) ?? "loading";
2130
});
2231

32+
const attributes = options?.attributes;
33+
34+
// biome-ignore lint/correctness/useExhaustiveDependencies: We convert the attributes object to a string to see if it has changed, so it can't be detected by the rule
2335
useEffect(() => {
2436
if (!url) {
2537
setStatus("idle");
@@ -34,6 +46,7 @@ export function useScript(url?: string): ScriptStatus {
3446
script.src = url;
3547
script.async = true;
3648
script.setAttribute("data-status", "loading");
49+
3750
document.body.appendChild(script);
3851

3952
// Ensure the status is loading
@@ -42,6 +55,14 @@ export function useScript(url?: string): ScriptStatus {
4255
setStatus(script.getAttribute("data-status") as ScriptStatus);
4356
}
4457

58+
if (attributes) {
59+
// Add extra attributes to the script element
60+
// If for some reason you have conflicting attributes, the last hook to execute will win
61+
Object.entries(attributes).forEach(([key, value]) => {
62+
script.setAttribute(key, value);
63+
});
64+
}
65+
4566
const eventHandler = (e: Event) => {
4667
const status: ScriptStatus = e.type === "load" ? "ready" : "error";
4768
script.setAttribute("data-status", status);
@@ -56,7 +77,7 @@ export function useScript(url?: string): ScriptStatus {
5677
script.removeEventListener("load", eventHandler);
5778
script.removeEventListener("error", eventHandler);
5879
};
59-
}, [url]);
80+
}, [url, attributes ? JSON.stringify(attributes) : undefined]);
6081

6182
return status;
6283
}

0 commit comments

Comments
 (0)