Skip to content

Instantly share code, notes, and snippets.

@geirsagberg
Created November 21, 2022 12:14
Show Gist options
  • Save geirsagberg/e95368d4b948038c1a9e7738761b3dc7 to your computer and use it in GitHub Desktop.
Save geirsagberg/e95368d4b948038c1a9e7738761b3dc7 to your computer and use it in GitHub Desktop.
import { useEffect, useState } from 'react'
export type ScriptStatus = 'idle' | 'loading' | 'ready' | 'error'
export function useScript(src: string): ScriptStatus {
// Keep track of script status ("idle", "loading", "ready", "error")
const [status, setStatus] = useState<ScriptStatus>(src ? 'loading' : 'idle')
useEffect(
() => {
// Allow falsy src value if waiting on other data needed for
// constructing the script URL passed to this hook.
if (!src) {
setStatus('idle')
return
}
// Fetch existing script element by src
// It may have been added by another intance of this hook
let script = document.querySelector<HTMLScriptElement>(
`script[src="${src}"]`
)
if (script) {
// Grab existing script status from attribute and set to state.
setStatus(
(script.getAttribute('data-status') as ScriptStatus) ?? 'idle'
)
} else {
// Create script
script = document.createElement('script')
script.src = src
script.async = true
script.setAttribute('data-status', 'loading')
// Add script to document body
document.body.appendChild(script)
// Store status in attribute on script
// This can be read by other instances of this hook
const setAttributeFromEvent = (event: Event) => {
script?.setAttribute(
'data-status',
event.type === 'load' ? 'ready' : 'error'
)
}
script.addEventListener('load', setAttributeFromEvent)
script.addEventListener('error', setAttributeFromEvent)
}
// 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) => {
setStatus(event.type === 'load' ? 'ready' : 'error')
}
// Add event listeners
script.addEventListener('load', setStateFromEvent)
script.addEventListener('error', setStateFromEvent)
// Remove event listeners on cleanup
return () => {
if (script) {
script.removeEventListener('load', setStateFromEvent)
script.removeEventListener('error', setStateFromEvent)
}
}
},
[src] // Only re-run effect if script src changes
)
return status
}
export default useScript
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment