Skip to content

Instantly share code, notes, and snippets.

@mike-at-redspace
Last active March 1, 2023 16:50
Show Gist options
  • Save mike-at-redspace/e8076033ece3392b824153fa311ca3b7 to your computer and use it in GitHub Desktop.
Save mike-at-redspace/e8076033ece3392b824153fa311ca3b7 to your computer and use it in GitHub Desktop.
Import on Interaction
import React, { useState } from 'react'
import useScript from './useScript'
import useFirstInteraction from './useFirstInteraction'
const App = () => {
const [{ loaded, error }, triggerScript] = useScript('<url-to-script>')
useFirstInteraction(() => {
triggerScript()
}, 5)
return (
<div>
{!loaded && !error && <p>Script is not loaded yet</p>}
{loaded && !error && <p>Script is loaded</p>}
{error && <p>An error occurred while loading the script</p>}
</div>
)
}
export default App
import { useEffect } from 'react'
const useFirstInteraction = (callback, delay) => {
const controller = new AbortController()
const { signal } = controller
useEffect(() => {
let timeoutId = null
const handleInteraction = () => {
callback()
controller.abort()
}
const handleDelay = () => {
timeoutId = setTimeout(() => {
handleInteraction()
}, delay * 1000)
}
document.addEventListener('scroll', handleInteraction, { signal })
document.addEventListener('click', handleInteraction, { signal })
document.addEventListener('keydown', handleInteraction, { signal })
document.addEventListener('touchstart', handleInteraction, { signal })
handleDelay()
return () => {
clearTimeout(timeoutId)
controller.abort()
}
}, [callback, delay])
}
export default useFirstInteraction
import { useState } from 'react'
const useScript = url => {
const [state, setState] = useState({
loaded: false,
error: false
})
const onLoad = () => {
setState({
loaded: true,
error: false
})
}
const onError = () => {
setState({
loaded: false,
error: true
})
}
const addScript = url => {
const script = document.createElement('script')
script.src = url
script.async = true
script.addEventListener('load', onLoad)
script.addEventListener('error', onError)
document.body.appendChild(script)
return () => {
script.removeEventListener('load', onLoad)
script.removeEventListener('error', onError)
document.body.removeChild(script)
}
}
const triggerScript = () => {
setState({
loaded: false,
error: false
})
addScript(url)
}
return [state, triggerScript]
}
export default useScript
import { render, cleanup, fireEvent } from '@testing-library/react'
import useScript from './useScript'
jest.mock('./script.js', () => {
return jest.fn()
})
afterEach(cleanup)
describe('useScript', () => {
it('should load the script and update the state', () => {
const url = './script.js'
const TestComponent = () => {
const [state, triggerScript] = useScript(url)
return (
<div>
<button data-testid='load-button' onClick={triggerScript}>
Load Script
</button>
<div data-testid='loaded-state'>
{state.loaded ? 'Loaded' : 'Not Loaded'}
</div>
</div>
)
}
const { getByTestId } = render(<TestComponent />)
fireEvent.click(getByTestId('load-button'))
expect(getByTestId('loaded-state').textContent).toBe('Loaded')
})
it('should handle script load error and update the state', () => {
const url = './bad-script.js'
const TestComponent = () => {
const [state, triggerScript] = useScript(url)
return (
<div>
<button data-testid='load-button' onClick={triggerScript}>
Load Script
</button>
<div data-testid='error-state'>
{state.error ? 'Error' : 'No Error'}
</div>
</div>
)
}
const { getByTestId } = render(<TestComponent />)
fireEvent.click(getByTestId('load-button'))
expect(getByTestId('error-state').textContent).toBe('Error')
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment