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'
2
3export type Status = 'idle' | 'loading' | 'ready' | 'error'
4export type ScriptElt = HTMLScriptElement | null
5
6function useScript(src: string): Status {
7 const [status, setStatus] = useState<Status>(src ? 'loading' : 'idle')
8
9 useEffect(
10 () => {
11 if (!src) {
12 setStatus('idle')
13 return
14 }
15
16 // Fetch existing script element by src
17 // It may have been added by another instance of this hook
18 let script: ScriptElt = document.querySelector(`script[src="${src}"]`)
19
20 if (!script) {
21 // Create script
22 script = document.createElement('script')
23 script.src = src
24 script.async = true
25 script.setAttribute('data-status', 'loading')
26 // Add script to document body
27 document.body.appendChild(script)
28
29 // Store status in attribute on script
30 // This can be read by other instances of this hook
31 const setAttributeFromEvent = (event: Event) => {
32 script?.setAttribute(
33 'data-status',
34 event.type === 'load' ? 'ready' : 'error',
35 )
36 }
37
38 script.addEventListener('load', setAttributeFromEvent)
39 script.addEventListener('error', setAttributeFromEvent)
40 } else {
41 // Grab existing script status from attribute and set to state.
42 setStatus(script.getAttribute('data-status') as Status)
43 }
44
45 // Script event handler to update status in state
46 // Note: Even if the script already exists we still need to add
47 // event handlers to update the state for *this* hook instance.
48 const setStateFromEvent = (event: Event) => {
49 setStatus(event.type === 'load' ? 'ready' : 'error')
50 }
51
52 // Add event listeners
53 script.addEventListener('load', setStateFromEvent)
54 script.addEventListener('error', setStateFromEvent)
55
56 // Remove event listeners on cleanup
57 return () => {
58 if (script) {
59 script.removeEventListener('load', setStateFromEvent)
60 script.removeEventListener('error', setStateFromEvent)
61 }
62 }
63 },
64 [src], // Only re-run effect if script src changes
65 )
66
67 return status
68}
69
70export default useScript

Usage

1import React, { useEffect } from 'react'
2
3import useScript from './useScript'
4
5// it's an example, use your types instead
6declare const jQuery: any
7
8export default function Component() {
9 // Load the script asynchronously
10 const status = useScript(`https://code.jquery.com/jquery-3.5.1.min.js`)
11
12 useEffect(() => {
13 if (typeof jQuery !== 'undefined') {
14 // jQuery is loaded => print the version
15 alert(jQuery.fn.jquery)
16 }
17 }, [status])
18
19 return (
20 <div>
21 <p>{`Current status: ${status}`}</p>
22
23 {status === 'ready' && <p>You can use the script here.</p>}
24 </div>
25 )
26}

Created:
7 months ago