@supunlakmal/hooks
Version:
A collection of reusable React hooks
96 lines • 5.28 kB
JavaScript
import { useState, useEffect, useRef } from 'react';
/**
* Hook to dynamically load an external script and track its loading state.
*
* @param src The source URL of the script to load. Pass null/undefined to unload or skip loading.
* @param options Configuration options for loading and handling the script.
* @returns The loading status of the script ('idle', 'loading', 'ready', 'error').
*/
export function useScript(src, options) {
const [status, setStatus] = useState(src ? 'loading' : 'idle');
const optionsRef = useRef(options); // Ref to keep options up-to-date without triggering effect
// Update optionsRef if options object changes identity
useEffect(() => {
optionsRef.current = options;
}, [options]);
useEffect(() => {
var _a, _b, _c, _d, _e;
if (!src) {
setStatus('idle');
return; // No source URL provided, do nothing
}
// Check if the script tag with this src already exists
let script = document.querySelector(`script[src="${src}"]`);
if (!script) {
// Script doesn't exist, create and append it
script = document.createElement('script');
script.src = src;
script.async = true; // Default to async
// Apply custom attributes from options
const currentAttrs = (_a = optionsRef.current) === null || _a === void 0 ? void 0 : _a.attrs;
if (currentAttrs) {
Object.keys(currentAttrs).forEach((key) => {
// Handle boolean attributes correctly
if (typeof currentAttrs[key] === 'boolean') {
if (currentAttrs[key]) {
script === null || script === void 0 ? void 0 : script.setAttribute(key, ''); // Presence indicates true (e.g., async, defer)
}
}
else {
script === null || script === void 0 ? void 0 : script.setAttribute(key, currentAttrs[key]);
}
});
}
document.body.appendChild(script);
setStatus('loading');
}
else if (script.getAttribute('data-status') === 'ready') {
// Script already exists and is marked as ready by a previous instance of this hook
setStatus('ready');
(_c = (_b = optionsRef.current) === null || _b === void 0 ? void 0 : _b.onLoad) === null || _c === void 0 ? void 0 : _c.call(_b); // Call onLoad if already ready
return; // No need for event listeners if already loaded
}
else if (script.getAttribute('data-status') === 'error') {
setStatus('error');
(_e = (_d = optionsRef.current) === null || _d === void 0 ? void 0 : _d.onError) === null || _e === void 0 ? void 0 : _e.call(_d, 'Script previously failed to load.');
return; // No need for event listeners if previously errored
}
// If script exists but status is unknown (or still loading), attach listeners
// Event listeners
const handleLoad = () => {
var _a, _b;
script === null || script === void 0 ? void 0 : script.setAttribute('data-status', 'ready'); // Mark as ready for other hook instances
setStatus('ready');
(_b = (_a = optionsRef.current) === null || _a === void 0 ? void 0 : _a.onLoad) === null || _b === void 0 ? void 0 : _b.call(_a);
};
const handleError = (event) => {
var _a, _b;
script === null || script === void 0 ? void 0 : script.setAttribute('data-status', 'error'); // Mark as error
setStatus('error');
(_b = (_a = optionsRef.current) === null || _a === void 0 ? void 0 : _a.onError) === null || _b === void 0 ? void 0 : _b.call(_a, event);
};
script.addEventListener('load', handleLoad);
script.addEventListener('error', handleError);
// Cleanup function
return () => {
var _a, _b;
// Remove listeners
script === null || script === void 0 ? void 0 : script.removeEventListener('load', handleLoad);
script === null || script === void 0 ? void 0 : script.removeEventListener('error', handleError);
// Optionally remove the script tag on unmount
const shouldRemove = (_b = (_a = optionsRef.current) === null || _a === void 0 ? void 0 : _a.removeOnUnmount) !== null && _b !== void 0 ? _b : true;
if (shouldRemove && script && document.body.contains(script)) {
// Only remove if no other hook instance might still need it
// Basic check: see if another identical script tag exists.
// A more robust solution might involve reference counting if multiple
// components load the same script with removeOnUnmount=true.
const allScripts = document.querySelectorAll(`script[src="${src}"]`);
if (allScripts.length === 1) {
document.body.removeChild(script);
}
}
};
}, [src]); // Re-run effect only if src changes
return status;
}
//# sourceMappingURL=useScript.js.map