diff --git a/src/useScript/useScript.demo.tsx b/src/useScript/useScript.demo.tsx index cf93c20b..d52aeb98 100644 --- a/src/useScript/useScript.demo.tsx +++ b/src/useScript/useScript.demo.tsx @@ -24,7 +24,7 @@ export default function Component() {

{`Current status: ${status}`}

- {status === 'ok' &&

You can use the script here.

} + {status === 'ready' &&

You can use the script here.

}
) } diff --git a/src/useScript/useScript.ts b/src/useScript/useScript.ts index 61cfd4eb..a2314bb0 100644 --- a/src/useScript/useScript.ts +++ b/src/useScript/useScript.ts @@ -1,16 +1,11 @@ import { useEffect, useState } from 'react' -export type UseScriptStatus = 'idle' | 'loading' | 'ok' | 'error' +export type UseScriptStatus = 'idle' | 'loading' | 'ready' | 'error' export interface UseScriptOptions { - shouldLoad?: boolean + shouldPreventLoad?: boolean removeOnUnmount?: boolean } -const defaultOptions: UseScriptOptions = { - shouldLoad: true, - removeOnUnmount: true, -} - // Cached script statuses const cachedScriptStatuses: Record = {} @@ -30,11 +25,10 @@ function getScriptNode(src: string) { function useScript( src: string | null, - options: UseScriptOptions = defaultOptions, + options?: UseScriptOptions, ): UseScriptStatus { - const { shouldLoad, removeOnUnmount } = options const [status, setStatus] = useState(() => { - if (!shouldLoad || !src) { + if (!src || options?.shouldPreventLoad) { return 'idle' } @@ -46,71 +40,72 @@ function useScript( return cachedScriptStatuses[src] ?? 'loading' }) - useEffect( - () => { - if (!src || !shouldLoad) { - return - } + useEffect(() => { + if (!src || options?.shouldPreventLoad) { + return + } - const cachedScriptStatus = cachedScriptStatuses[src] - if (cachedScriptStatus === 'ok' || cachedScriptStatus === 'error') { - // If the script is already cached, set its status immediately - setStatus(cachedScriptStatus) - return - } + const cachedScriptStatus = cachedScriptStatuses[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 - 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' ? 'ok' : '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 ?? cachedScriptStatuses[src] ?? 'loading') + // 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 + 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) } - // 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' ? 'ok' : 'error' - setStatus(newStatus) - cachedScriptStatuses[src] = newStatus - } + 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[src] = newStatus + } - // Add event listeners - scriptNode.addEventListener('load', setStateFromEvent) - scriptNode.addEventListener('error', setStateFromEvent) + // Add event listeners + scriptNode.addEventListener('load', setStateFromEvent) + scriptNode.addEventListener('error', setStateFromEvent) - // Remove event listeners on cleanup - return () => { - if (scriptNode && removeOnUnmount) { - scriptNode.removeEventListener('load', setStateFromEvent) - scriptNode.removeEventListener('error', setStateFromEvent) - } + // Remove event listeners on cleanup + return () => { + if (scriptNode) { + scriptNode.removeEventListener('load', setStateFromEvent) + scriptNode.removeEventListener('error', setStateFromEvent) } - }, - [src, shouldLoad, removeOnUnmount], // Only re-run effect if script src changes - ) + + if (scriptNode && options?.removeOnUnmount) { + scriptNode.remove() + } + } + }, [src, options?.shouldPreventLoad, options?.removeOnUnmount]) return status }