UNPKG

mod-arch-shared

Version:

Shared library for modular architecture micro-frontend projects

153 lines 5.75 kB
import * as React from 'react'; /** * Allows "I'm not ready" rejections if you lack a lazy provided prop * e.g. Promise.reject(new NotReadyError('Do not have namespace')) */ export class NotReadyError extends Error { constructor(reason) { super(`Not ready yet. ${reason}`); this.name = 'NotReadyError'; } } /** * Checks to see if it's a standard error handled by useStateFetch .catch block. */ export const isCommonStateError = (e) => { if (e.name === 'NotReadyError') { // An escape hatch for callers to reject the call at this fetchCallbackPromise reference // Re-compute your callback to re-trigger again return true; } if (e.name === 'AbortError') { // Abort errors are silent return true; } return false; }; const isAdHocUpdate = (r) => typeof r === 'function'; /** * A boilerplate helper utility. Given a callback that returns a promise, it will store state and * handle refreshes on intervals as needed. * * Note: Your callback *should* support the opts property so the call can be cancelled. */ export const useFetchState = ( /** React.useCallback result. */ fetchCallbackPromise, /** * A preferred default states - this is ignored after the first render * Note: This is only read as initial value; changes do nothing. */ initialDefaultState, /** Configurable features */ { refreshRate = 0, initialPromisePurity = false } = {}) => { const initialDefaultStateRef = React.useRef(initialDefaultState); const [result, setResult] = React.useState(initialDefaultState); const [loaded, setLoaded] = React.useState(false); const [loadError, setLoadError] = React.useState(undefined); const abortCallbackRef = React.useRef(() => undefined); const changePendingRef = React.useRef(true); /** Setup on initial hook a singular reset function. DefaultState & resetDataOnNewPromise are initial render states. */ const cleanupRef = React.useRef(() => { if (initialPromisePurity) { setResult(initialDefaultState); setLoaded(false); setLoadError(undefined); } }); React.useEffect(() => { cleanupRef.current(); }, [fetchCallbackPromise]); const call = React.useCallback(() => { let alreadyAborted = false; const abortController = new AbortController(); /** Note: this promise cannot "catch" beyond this instance -- unless a runtime error. */ const doRequest = () => fetchCallbackPromise({ signal: abortController.signal }) .then((r) => { changePendingRef.current = false; if (alreadyAborted) { return undefined; } if (r === undefined) { // Undefined is an unacceptable response. If you want "nothing", pass `null` -- this is likely an API issue though. // eslint-disable-next-line no-console console.error('useFetchState Error: Got undefined back from a promise. This is likely an error with your call. Preventing setting.'); return undefined; } setLoadError(undefined); if (isAdHocUpdate(r)) { r((setState) => { if (alreadyAborted) { return undefined; } setResult(setState); setLoaded(true); return undefined; }); return undefined; } setResult(r); setLoaded(true); return r; }) .catch((e) => { changePendingRef.current = false; if (alreadyAborted) { return undefined; } if (isCommonStateError(e)) { return undefined; } setLoadError(e); return undefined; }); const unload = () => { changePendingRef.current = false; if (alreadyAborted) { return; } alreadyAborted = true; abortController.abort(); }; return [doRequest(), unload]; }, [fetchCallbackPromise]); // Use a memmo to update the `changePendingRef` immediately on change. React.useMemo(() => { changePendingRef.current = true; // React to changes to the `call` reference. // eslint-disable-next-line react-hooks/exhaustive-deps }, [call]); React.useEffect(() => { let interval; const callAndSave = () => { const [, unload] = call(); abortCallbackRef.current = unload; }; callAndSave(); if (refreshRate > 0) { interval = setInterval(() => { abortCallbackRef.current(); callAndSave(); }, refreshRate); } return () => { clearInterval(interval); abortCallbackRef.current(); }; }, [call, refreshRate]); // Use a reference for `call` to ensure a stable reference to `refresh` is always returned const callRef = React.useRef(call); callRef.current = call; const refresh = React.useCallback(() => { abortCallbackRef.current(); const [callPromise, unload] = callRef.current(); abortCallbackRef.current = unload; return callPromise; }, []); // Return the default reset state if a change is pending and initialPromisePurity is true if (initialPromisePurity && changePendingRef.current) { return [initialDefaultStateRef.current, false, undefined, refresh]; } return [result, loaded, loadError, refresh]; }; //# sourceMappingURL=useFetchState.js.map