Created
August 9, 2024 11:29
-
-
Save SarKurd/7521109a6f0eaff157353ef6e0fe8ab6 to your computer and use it in GitHub Desktop.
Load scripts in React TS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { useEffect, useState } from 'react' | |
| type UseScriptStatus = 'idle' | 'loading' | 'ready' | 'error' | |
| type UseScriptOptions = { | |
| removeOnUnmount?: boolean | |
| id?: string | |
| } | |
| // Cached script statuses | |
| const cachedScriptStatuses = new Map<string, UseScriptStatus | undefined>() | |
| function getScriptNode(src: string) { | |
| const node = document.querySelector<HTMLScriptElement>(`script[src="${src}"]`) | |
| const status = node?.getAttribute('data-status') as | |
| | UseScriptStatus | |
| | undefined | |
| return { | |
| node, | |
| status, | |
| } | |
| } | |
| function useScript( | |
| src?: string, | |
| options?: UseScriptOptions | |
| ): { | |
| status: UseScriptStatus | |
| node: HTMLScriptElement | null | |
| } { | |
| const [status, setStatus] = useState<UseScriptStatus>(() => { | |
| if (!src) { | |
| return 'idle' | |
| } | |
| if (typeof window === 'undefined') { | |
| // SSR Handling - always return 'loading' | |
| return 'loading' | |
| } | |
| return cachedScriptStatuses.get(src) ?? 'loading' | |
| }) | |
| const [node, setNode] = useState<HTMLScriptElement | null>(null) | |
| useEffect(() => { | |
| if (!src) { | |
| 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') | |
| } | |
| setNode(scriptNode) | |
| // 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 (options?.removeOnUnmount) { | |
| scriptNode.remove() | |
| cachedScriptStatuses.delete(src) | |
| } | |
| } | |
| } | |
| }, [src, options?.removeOnUnmount, options?.id]) | |
| return { status, node } | |
| } | |
| export default useScript |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment