UNPKG

@supunlakmal/hooks

Version:

A collection of reusable React hooks

96 lines 5.28 kB
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