Every time the interval is executed or the window gets focused or blurred the entire app re-renders. I fixed the hook but don't have any time to create a PR. If you find any issues in my code, please leave a comment. With my code you can also remove 2 dependencies.
Feel free to use it:
import {
useCallback,
useEffect,
useRef,
useState,
} from 'react';
const getCurrentVersion = async (endpoint: string) => {
const response = await fetch(endpoint);
if (response.status > 400) {
console.error(
'[next-deploy-notifications] Could not find current app version. Did you setup the API route?',
);
return { version: 'unknown' };
}
const json = await response.json();
return json;
};
type HookOptions = {
interval?: number;
endpoint?: string;
debug?: boolean;
};
type HookValues = {
hasNewDeploy: boolean;
version: string;
};
type UseHasNewDeploy = (options?: HookOptions) => HookValues;
const useHasNewDeploy: UseHasNewDeploy = (options = {}) => {
const debug = useCallback((message: string) => {
if (options.debug) {
console.log(...['[Deploy notifications] ', message]);
}
}, [options.debug]);
const [hasNewDeploy, setHasNewDeploy] = useState<boolean>(false);
const [currentVersion, setCurrentVersion] = useState<string>('unknown');
const lastFetchedRef = useRef<number>();
const intervalInstanceRef = useRef<NodeJS.Timer>();
const windowFocused = useRef<boolean>(true);
const interval = options.interval ?? 30_000;
const loopInterval = interval < 3_000 ? interval : 3_000;
const endpoint = options.endpoint ?? '/api/has-new-deploy';
const isUnknown = currentVersion === 'unknown';
const startInterval = useCallback(
() => setInterval(async () => {
debug('Looping...');
const enoughTimeHasPassed =
!lastFetchedRef.current || Date.now() >= lastFetchedRef.current + interval;
if (enoughTimeHasPassed && !isUnknown) {
debug('Fetching version');
const { version } = await getCurrentVersion(endpoint);
debug(`Version ${version}`);
if (currentVersion !== version) {
debug('Found new deploy');
setHasNewDeploy(true);
setCurrentVersion(version);
}
lastFetchedRef.current = Date.now();
}
}, loopInterval),
[currentVersion, debug, endpoint, interval, isUnknown, loopInterval],
);
useEffect(() => {
if (!hasNewDeploy) return;
clearInterval(intervalInstanceRef.current);
}, [hasNewDeploy]);
useEffect(() => {
if (!hasNewDeploy && windowFocused.current) {
clearInterval(intervalInstanceRef.current);
intervalInstanceRef.current = startInterval();
}
const onFocus = () => {
debug('focus');
windowFocused.current = true;
clearInterval(intervalInstanceRef.current);
intervalInstanceRef.current = startInterval();
};
const onBlur = () => {
debug('blur');
windowFocused.current = false;
clearInterval(intervalInstanceRef.current);
};
debug('addEventListeners');
window.addEventListener('focus', onFocus);
window.addEventListener('blur', onBlur);
return () => {
debug('removeEventListeners');
window.removeEventListener('focus', onFocus);
window.removeEventListener('blur', onBlur);
};
}, []);
useEffect(() => {
const fetchInitialVersion = async () => {
debug('Fetching initial version');
const { version } = await getCurrentVersion(endpoint);
if (version === 'unknown') {
console.warn(
'[next-deploy-notifications] Could not find current app version.',
);
} else {
debug(`Version ${version}`);
setCurrentVersion(version);
lastFetchedRef.current = Date.now();
}
};
fetchInitialVersion();
}, [endpoint, debug]);
return {
hasNewDeploy,
version: currentVersion,
};
};
export { useHasNewDeploy };