mirror of https://gitee.com/answerdev/answer.git
fix(setupApp): Update setupApp logic to prevent invalid Promise.allSettled from being triggered
This commit is contained in:
parent
c843dddf0b
commit
f46af67b2c
|
@ -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}
|
||||||
|
|
|
@ -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: () => {
|
||||||
|
|
|
@ -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') {
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue