UNPKG

react-promise-tracker

Version:

Simple React Promise tracker Hook/HOC helper to add loading spinner indicators

66 lines (62 loc) 2.91 kB
import React from "react"; import { emitter, promiseCounterUpdateEventId, getCounter } from "./trackPromise"; import { defaultConfig, setupConfig } from "./setupConfig"; export const usePromiseTracker = (outerConfig = defaultConfig) => { let isMounted = React.useRef(false); React.useEffect(() => { isMounted.current = true; return () => isMounted.current = false; }, []); // Included in state, it will be evaluated just the first time, // TODO: discuss if this is a good approach // We need to apply defensive programming, ensure area and delay default to secure data // cover cases like not all params informed, set secure defaults const [config] = React.useState(setupConfig(outerConfig)); // Edge case, when we start the application if we are loading just onComponentDidMount // data, event emitter could have already emitted the event but subscription is not yet // setup React.useEffect(() => { if (isMounted.current && config && config.area && getCounter(config.area) > 0) { setInternalPromiseInProgress(true); setPromiseInProgress(true); } }, [config]); // Internal will hold the current value const [internalPromiseInProgress, setInternalPromiseInProgress] = React.useState(false); // Promise in progress is 'public', it can be affected by the _delay_ parameter // it may not show the current state const [promiseInProgress, setPromiseInProgress] = React.useState(false); // We need to hold a ref to latestInternal, to check the real value on // callbacks (if not we would get always the same value) // more info: https://overreacted.io/a-complete-guide-to-useeffect/ const latestInternalPromiseInProgress = React.useRef(internalPromiseInProgress); const notifyPromiseInProgress = () => { !config || !config.delay || config.delay === 0 ? setPromiseInProgress(true) : setTimeout(() => { // Check here ref to internalPromiseInProgress if (isMounted.current && latestInternalPromiseInProgress.current) { setPromiseInProgress(true); } }, config.delay); }; const updatePromiseTrackerStatus = (anyPromiseInProgress, areaAffected) => { if (isMounted.current && config.area === areaAffected) { setInternalPromiseInProgress(anyPromiseInProgress); // Update the ref object as well, we will check it when we need to // cover the _delay_ case (setTimeout) latestInternalPromiseInProgress.current = anyPromiseInProgress; if (!anyPromiseInProgress) { setPromiseInProgress(false); } else { notifyPromiseInProgress(); } } }; React.useEffect(() => { latestInternalPromiseInProgress.current = internalPromiseInProgress; emitter.on(promiseCounterUpdateEventId, updatePromiseTrackerStatus); return () => emitter.off(promiseCounterUpdateEventId, updatePromiseTrackerStatus); }, []); return { promiseInProgress }; };