useScript
Custom hook that dynamically loads scripts and tracking their loading status.
Usage
import { useEffect } from 'react' import { useScript } from 'usehooks-ts' // it's an example, use your types instead declare const jQuery: any export default function Component() { // Load the script asynchronously const status = useScript(`https://code.jquery.com/jquery-3.5.1.min.js`, { removeOnUnmount: false, id: 'jquery', }) useEffect(() => { if (typeof jQuery !== 'undefined') { // jQuery is loaded => print the version // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access alert(jQuery.fn.jquery) } }, [status]) return ( <div> <p>{`Current status: ${status}`}</p> {status === 'ready' && <p>You can use the script here.</p>} </div> ) } API
▸ useScript(src, options?): UseScriptStatus
Custom hook that dynamically loads scripts and tracking their loading status.
Parameters
| Name | Type | Description |
|---|---|---|
src | null | string | The source URL of the script to load. Set to null or omit to prevent loading (optional). |
options? | UseScriptOptions | Additional options for controlling script loading (optional). |
Returns
The status of the script loading, which can be one of 'idle', 'loading', 'ready', or 'error'.
Type aliases
Ƭ UseScriptOptions: Object
Hook options.
Type declaration
| Name | Type | Description |
|---|---|---|
id? | string | Script's id (optional). |
removeOnUnmount? | boolean | If true, removes the script from the DOM when the component unmounts (optional). |
shouldPreventLoad? | boolean | If true, prevents the script from being loaded (optional). |
Ƭ UseScriptStatus: "idle" | "loading" | "ready" | "error"
Script loading status.
Hook
import { useEffect, useState } from 'react' type UseScriptStatus = 'idle' | 'loading' | 'ready' | 'error' type UseScriptOptions = { shouldPreventLoad?: boolean removeOnUnmount?: boolean id?: string } // Cached script statuses const cachedScriptStatuses = new Map<string, UseScriptStatus | undefined>() function getScriptNode(src: string) { const node: HTMLScriptElement | null = document.querySelector( `script[src="${src}"]`, ) const status = node?.getAttribute('data-status') as | UseScriptStatus | undefined return { node, status, } } export function useScript( src: string | null, options?: UseScriptOptions, ): UseScriptStatus { const [status, setStatus] = useState<UseScriptStatus>(() => { if (!src || options?.shouldPreventLoad) { return 'idle' } if (typeof window === 'undefined') { // SSR Handling - always return 'loading' return 'loading' } return cachedScriptStatuses.get(src) ?? 'loading' }) useEffect(() => { if (!src || options?.shouldPreventLoad) { return } const cachedScriptStatus = cachedScriptStatuses.get(src) if (cachedScriptStatus === 'ready' || cachedScriptStatus === 'error') { // If the script is already cached, set its status immediately setStatus(cachedScriptStatus) return } // Fetch existing script element by src // It may have been added by another instance of this hook const script = getScriptNode(src) let scriptNode = script.node if (!scriptNode) { // Create script element and add it to document body scriptNode = document.createElement('script') scriptNode.src = src scriptNode.async = true if (options?.id) { scriptNode.id = options.id } scriptNode.setAttribute('data-status', 'loading') document.body.appendChild(scriptNode) // Store status in attribute on script // This can be read by other instances of this hook const setAttributeFromEvent = (event: Event) => { const scriptStatus: UseScriptStatus = event.type === 'load' ? 'ready' : 'error' scriptNode?.setAttribute('data-status', scriptStatus) } scriptNode.addEventListener('load', setAttributeFromEvent) scriptNode.addEventListener('error', setAttributeFromEvent) } else { // Grab existing script status from attribute and set to state. setStatus(script.status ?? cachedScriptStatus ?? 'loading') } // Script event handler to update status in state // Note: Even if the script already exists we still need to add // event handlers to update the state for *this* hook instance. const setStateFromEvent = (event: Event) => { const newStatus = event.type === 'load' ? 'ready' : 'error' setStatus(newStatus) cachedScriptStatuses.set(src, newStatus) } // Add event listeners scriptNode.addEventListener('load', setStateFromEvent) scriptNode.addEventListener('error', setStateFromEvent) // Remove event listeners on cleanup return () => { if (scriptNode) { scriptNode.removeEventListener('load', setStateFromEvent) scriptNode.removeEventListener('error', setStateFromEvent) } if (scriptNode && options?.removeOnUnmount) { scriptNode.remove() cachedScriptStatuses.delete(src) } } }, [src, options?.shouldPreventLoad, options?.removeOnUnmount, options?.id]) return status }