useScript()
Dynamically load an external script in one line with this React hook. This can be useful to integrate a third party library like Google Analytics or Stripe.
This avoids loading this script in the <head> </head>
on all your pages if it is not necessary.
The Hook
1import { useEffect, useState } from 'react'23export type UseScriptStatus = 'idle' | 'loading' | 'ready' | 'error'4export interface UseScriptOptions {5 shouldPreventLoad?: boolean6 removeOnUnmount?: boolean7}89// Cached script statuses10const cachedScriptStatuses: Record<string, UseScriptStatus | undefined> = {}1112function getScriptNode(src: string) {13 const node: HTMLScriptElement | null = document.querySelector(14 `script[src="${src}"]`,15 )16 const status = node?.getAttribute('data-status') as17 | UseScriptStatus18 | undefined1920 return {21 node,22 status,23 }24}2526function useScript(27 src: string | null,28 options?: UseScriptOptions,29): UseScriptStatus {30 const [status, setStatus] = useState<UseScriptStatus>(() => {31 if (!src || options?.shouldPreventLoad) {32 return 'idle'33 }3435 if (typeof window === 'undefined') {36 // SSR Handling - always return 'loading'37 return 'loading'38 }3940 return cachedScriptStatuses[src] ?? 'loading'41 })4243 useEffect(() => {44 if (!src || options?.shouldPreventLoad) {45 return46 }4748 const cachedScriptStatus = cachedScriptStatuses[src]49 if (cachedScriptStatus === 'ready' || cachedScriptStatus === 'error') {50 // If the script is already cached, set its status immediately51 setStatus(cachedScriptStatus)52 return53 }5455 // Fetch existing script element by src56 // It may have been added by another instance of this hook57 const script = getScriptNode(src)58 let scriptNode = script.node5960 if (!scriptNode) {61 // Create script element and add it to document body62 scriptNode = document.createElement('script')63 scriptNode.src = src64 scriptNode.async = true65 scriptNode.setAttribute('data-status', 'loading')66 document.body.appendChild(scriptNode)6768 // Store status in attribute on script69 // This can be read by other instances of this hook70 const setAttributeFromEvent = (event: Event) => {71 const scriptStatus: UseScriptStatus =72 event.type === 'load' ? 'ready' : 'error'7374 scriptNode?.setAttribute('data-status', scriptStatus)75 }7677 scriptNode.addEventListener('load', setAttributeFromEvent)78 scriptNode.addEventListener('error', setAttributeFromEvent)79 } else {80 // Grab existing script status from attribute and set to state.81 setStatus(script.status ?? cachedScriptStatus ?? 'loading')82 }8384 // Script event handler to update status in state85 // Note: Even if the script already exists we still need to add86 // event handlers to update the state for *this* hook instance.87 const setStateFromEvent = (event: Event) => {88 const newStatus = event.type === 'load' ? 'ready' : 'error'89 setStatus(newStatus)90 cachedScriptStatuses[src] = newStatus91 }9293 // Add event listeners94 scriptNode.addEventListener('load', setStateFromEvent)95 scriptNode.addEventListener('error', setStateFromEvent)9697 // Remove event listeners on cleanup98 return () => {99 if (scriptNode) {100 scriptNode.removeEventListener('load', setStateFromEvent)101 scriptNode.removeEventListener('error', setStateFromEvent)102 }103104 if (scriptNode && options?.removeOnUnmount) {105 scriptNode.remove()106 }107 }108 }, [src, options?.shouldPreventLoad, options?.removeOnUnmount])109110 return status111}112113export default useScript
Usage
1import { useEffect } from 'react'23import { useScript } from 'usehooks-ts'45// it's an example, use your types instead6// eslint-disable-next-line @typescript-eslint/no-explicit-any7declare const jQuery: any89export default function Component() {10 // Load the script asynchronously11 const status = useScript(`https://code.jquery.com/jquery-3.5.1.min.js`, {12 removeOnUnmount: false,13 })1415 useEffect(() => {16 if (typeof jQuery !== 'undefined') {17 // jQuery is loaded => print the version18 // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access19 alert(jQuery.fn.jquery)20 }21 }, [status])2223 return (24 <div>25 <p>{`Current status: ${status}`}</p>2627 {status === 'ready' && <p>You can use the script here.</p>}28 </div>29 )30}
See a way to make this page better?
Edit there »