fix(setupApp): Update setupApp logic to prevent invalid Promise.allSettled from being triggered

This commit is contained in:
haitaoo 2023-04-29 12:37:30 +08:00
parent c843dddf0b
commit f46af67b2c
5 changed files with 50 additions and 40 deletions

View File

@ -1,5 +1,5 @@
import { FC, ReactNode, useEffect, useState } from 'react'; import { FC, ReactNode, useEffect, useState } from 'react';
import { useLocation, useNavigate, useLoaderData } from 'react-router-dom'; import { useNavigate, useLoaderData } from 'react-router-dom';
import { floppyNavigation } from '@/utils'; import { floppyNavigation } from '@/utils';
import { TGuardFunc, TGuardResult } from '@/utils/guard'; import { TGuardFunc, TGuardResult } from '@/utils/guard';
@ -13,7 +13,6 @@ const RouteGuard: FC<{
page?: string; page?: string;
}> = ({ children, onEnter, path, page }) => { }> = ({ children, onEnter, path, page }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
const loaderData = useLoaderData(); const loaderData = useLoaderData();
const [gk, setKeeper] = useState<TGuardResult>({ const [gk, setKeeper] = useState<TGuardResult>({
ok: true, ok: true,
@ -23,6 +22,7 @@ const RouteGuard: FC<{
if (typeof onEnter !== 'function') { if (typeof onEnter !== 'function') {
return; return;
} }
const gr = onEnter({ const gr = onEnter({
loaderData, loaderData,
path, path,
@ -48,12 +48,10 @@ const RouteGuard: FC<{
}; };
useEffect(() => { useEffect(() => {
/** /**
* NOTICE: * By detecting changes to location.href, many unnecessary tests can be avoided
* Must be put in `useEffect`,
* otherwise `guard` may not get `loggedUserInfo` correctly
*/ */
applyGuard(); applyGuard();
}, [location]); }, [window.location.href]);
let asOK = gk.ok; let asOK = gk.ok;
if (gk.ok === false && gk.redirect) { if (gk.ok === false && gk.redirect) {
@ -62,10 +60,8 @@ const RouteGuard: FC<{
* but the current page is already the target page for the route guard jump * but the current page is already the target page for the route guard jump
* This should render `children`! * This should render `children`!
*/ */
asOK = floppyNavigation.equalToCurrentHref(gk.redirect); asOK = floppyNavigation.equalToCurrentHref(gk.redirect);
} }
return ( return (
<> <>
{asOK ? children : null} {asOK ? children : null}

View File

@ -269,7 +269,7 @@ const routes: RouteNode[] = [
path: 'admin', path: 'admin',
page: 'pages/Admin', page: 'pages/Admin',
loader: async () => { loader: async () => {
await guard.pullLoggedUser(true); await guard.pullLoggedUser();
return null; return null;
}, },
guard: () => { guard: () => {

View File

@ -71,6 +71,9 @@ const navigate = (to: string | number, config: NavigateConfig = {}) => {
if (!isRoutableLink(to) && handler !== 'href' && handler !== 'replace') { if (!isRoutableLink(to) && handler !== 'href' && handler !== 'replace') {
handler = 'href'; handler = 'href';
} }
if (handler === 'href' && config.options?.replace) {
handler = 'replace';
}
if (handler === 'href') { if (handler === 'href') {
window.location.href = to; window.location.href = to;
} else if (handler === 'replace') { } else if (handler === 'replace') {

View File

@ -20,7 +20,7 @@ import Storage from '@/utils/storage';
import { setupAppLanguage, setupAppTimeZone } from './localize'; import { setupAppLanguage, setupAppTimeZone } from './localize';
import { floppyNavigation, NavigateConfig } from './floppyNavigation'; import { floppyNavigation, NavigateConfig } from './floppyNavigation';
import { pullUcAgent, getLoginUrl, getSignUpUrl } from './userCenter'; import { pullUcAgent, getSignUpUrl } from './userCenter';
type TLoginState = { type TLoginState = {
isLogged: boolean; isLogged: boolean;
@ -105,15 +105,18 @@ export const isIgnoredPath = (ignoredPath: string | string[]) => {
return !!matchingPath; return !!matchingPath;
}; };
let pluLock = false;
let pluTimestamp = 0; let pluTimestamp = 0;
export const pullLoggedUser = async (forceRePull = false) => { export const pullLoggedUser = async (isInitPull = false) => {
// only pull once if not force re-pull /**
if (pluLock && !forceRePull) { * WARN:
return; * - dedupe pull requests in this time span in 10 seconds
} * - isInitPull:
// dedupe pull requests in this time span in 10 seconds * Requests sent by the initialisation method cannot be throttled
if (Date.now() - pluTimestamp < 1000 * 10) { * and may cause Promise.allSettled to complete early in React development mode,
* resulting in inaccurate application data.
*/
//
if (!isInitPull && Date.now() - pluTimestamp < 1000 * 10) {
return; return;
} }
pluTimestamp = Date.now(); pluTimestamp = Date.now();
@ -125,7 +128,6 @@ export const pullLoggedUser = async (forceRePull = false) => {
console.error(ex); console.error(ex);
}); });
if (loggedUserInfo) { if (loggedUserInfo) {
pluLock = true;
loggedUserInfoStore.getState().update(loggedUserInfo); loggedUserInfoStore.getState().update(loggedUserInfo);
} }
}; };
@ -241,16 +243,6 @@ export const allowNewRegistration = () => {
return gr; return gr;
}; };
export const loginAgent = () => {
const gr: TGuardResult = { ok: true };
const loginUrl = getLoginUrl();
if (loginUrl !== RouteAlias.login) {
gr.ok = false;
gr.redirect = loginUrl;
}
return gr;
};
export const singUpAgent = () => { export const singUpAgent = () => {
const gr: TGuardResult = { ok: true }; const gr: TGuardResult = { ok: true };
const signUpUrl = getSignUpUrl(); const signUpUrl = getSignUpUrl();
@ -367,7 +359,6 @@ export const handleLoginWithToken = (
/** /**
* Initialize app configuration * Initialize app configuration
*/ */
let appInitialized = false;
export const initAppSettingsStore = async () => { export const initAppSettingsStore = async () => {
const appSettings = await getAppSettings(); const appSettings = await getAppSettings();
if (appSettings) { if (appSettings) {
@ -389,7 +380,13 @@ export const initAppSettingsStore = async () => {
} }
}; };
let appInitialized = false;
export const setupApp = async () => { export const setupApp = async () => {
/**
* This cannot be removed:
* clicking on the current navigation link will trigger a call to the routing loader,
* even though the page is not refreshed.
*/
if (appInitialized) { if (appInitialized) {
return; return;
} }
@ -398,9 +395,14 @@ export const setupApp = async () => {
* 1. must pre init logged user info for router guard * 1. must pre init logged user info for router guard
* 2. must pre init app settings for app render * 2. must pre init app settings for app render
*/ */
await Promise.allSettled([pullLoggedUser(), initAppSettingsStore()]); await Promise.allSettled([initAppSettingsStore(), pullLoggedUser(true)]);
await Promise.allSettled([pullUcAgent()]); await Promise.allSettled([pullUcAgent()]);
setupAppLanguage(); setupAppLanguage();
setupAppTimeZone(); setupAppTimeZone();
/**
* WARN:
* Initialization must be completed after all initialization actions,
* otherwise the problem of rendering twice in React development mode can lead to inaccurate data or flickering pages
*/
appInitialized = true; appInitialized = true;
}; };

View File

@ -52,7 +52,6 @@ class Request {
// no content // no content
return true; return true;
} }
return data; return data;
}, },
(error) => { (error) => {
@ -62,9 +61,22 @@ class Request {
config: errConfig, config: errConfig,
} = error.response || {}; } = error.response || {};
const { data = {}, msg = '' } = errModel || {}; const { data = {}, msg = '' } = errModel || {};
const errorObject: {
code: any;
msg: string;
data: any;
// Currently only used for form errors
isError?: boolean;
// Currently only used for form errors
list?: any[];
} = {
code: status,
msg,
data,
};
if (status === 400) { if (status === 400) {
if (data?.err_type && errConfig?.passingError) { if (data?.err_type && errConfig?.passingError) {
return errModel; return Promise.reject(errorObject);
} }
if (data?.err_type) { if (data?.err_type) {
if (data.err_type === 'toast') { if (data.err_type === 'toast') {
@ -94,12 +106,9 @@ class Request {
if (data instanceof Array && data.length > 0) { if (data instanceof Array && data.length > 0) {
// handle form error // handle form error
return Promise.reject({ errorObject.isError = true;
code: status, errorObject.list = data;
msg, return Promise.reject(errorObject);
isError: true,
list: data,
});
} }
if (!data || Object.keys(data).length <= 0) { if (!data || Object.keys(data).length <= 0) {
@ -177,7 +186,7 @@ class Request {
`Request failed with status code ${status}, ${msg || ''}`, `Request failed with status code ${status}, ${msg || ''}`,
); );
} }
return Promise.reject(false); return Promise.reject(errorObject);
}, },
); );
} }