React 查询错误边界:如何根据用户是否第二次看到该组件为用户提供不同的选项
React query error boundaries: how to give users different options depending on whether they are seeing the component for the second time
我正在尝试改进使用 react-query
.
的应用程序中的错误处理
我有一个 <ErrorBoundary>
组件用于我的认证用户路由器:
<ErrorBoundaryRoot errorMessage="auth error">
<PracticeTypeContextProvider>
<IonReactRouter>
... myRoutesAndStuff
组件包装器如下所示:
import { ErrorBoundary } from 'react-error-boundary';
const ErrorBoundaryRoot: React.VFC<MyProps> = ({ children, errorMessage }: MyProps) => {
const { reset } = useQueryErrorResetBoundary();
return (
<ErrorBoundary
onReset={reset}
fallbackRender={({ resetErrorBoundary }) => (
<PageError errorMessage={errorMessage} resetErrorBoundary={resetErrorBoundary} />
)}
>
<Suspense fallback={<LoadingApp />}>
{ children }
</Suspense>
</ErrorBoundary>
);
};
这正确地捕获了错误,太棒了!
现在,我想向用户显示两个按钮之一:
- 如果是第一次看到错误边界,
<ButtonInvalidateAuthQueries>
,这样我就可以尝试在不注销用户的情况下重置应用程序。
- 但是,如果失败了,我想向用户显示一个不同的按钮,
<ButtonResetApp>
,它会重置所有查询并注销用户。
我这样设置 <PageError>
组件:
const PageError: React.VFC<MyProps> = ({ errorMessage, resetErrorBoundary }: MyProps) => {
const [alreadyTriedItOnce, setAlreadyTriedItOnce] = useState(false);
return (
<IonPage>
<IonContent>
<>
{ alreadyTriedItOnce ? (
<ButtonResetApp platform={platform} resetErrorBoundary={resetErrorBoundary} />
) : (
<ButtonInvalidateAuthQueries
setAlreadyTriedItOnce={setAlreadyTriedItOnce}
resetErrorBoundary={resetErrorBoundary}
/>
)}
</>
)}
</IonContent>
</IonPage>
);
};
以及无效按钮:
const ButtonInvalidateAuthQueries: React.VFC<MyProps> = (
{ setAlreadyTriedItOnce, resetErrorBoundary }: MyProps,
) => {
const queryClient = useQueryClient();
return (
<IonButton
onClick={() => {
queryClient.invalidateQueries(queryKeyInvalidateAllAuthQueries);
resetErrorBoundary();
setAlreadyTriedItOnce(true);
}}
>
Reset auth queries
</IonButton>
);
};
但是,这不起作用,因为当用户单击 <ButtonInvalidateAuthQueries>
时,所有的 auth 组件都会重新呈现,包括错误边界,因此如果再次显示错误边界,alreadyTriedItOnce
状态初始化为false。
所以我的问题是:是否有一些聪明的方法来判断 <ButtonInvalidateAuthQueries>
是否失败,如果是,则显示 <ButtonResetApp>
?
作为一种变通方法,我可以同时显示两个按钮,并向用户说明“先单击这个,如果不起作用,再单击另一个”,但我觉得作为一名程序员,这是我的工作在任何给定时间为用户提供最佳选择。
我根据 Bergi 的评论使用 Context 解决了这个问题。
首先,我创建了一个简单的上下文来存储布尔值 useState:
import React from 'react';
import useStateBoolean from '../utils/hooks/useStateBoolean';
const ErrorTriedAlreadyContext = React.createContext(false);
const ErrorTriedAlreadySetContext = React.createContext(
{} as React.Dispatch<React.SetStateAction<boolean>>,
);
interface MyProps {
children: JSX.Element,
}
export const ErrorTriedAlreadyContextProvider: React.VFC<MyProps> = ({ children }: MyProps) => {
const [triedAlready, setTriedAlready] = useStateBoolean();
return (
<ErrorTriedAlreadyContext.Provider value={triedAlready}>
<ErrorTriedAlreadySetContext.Provider value={setTriedAlready}>
{children}
</ErrorTriedAlreadySetContext.Provider>
</ErrorTriedAlreadyContext.Provider>
);
};
export const useErrorAlreadyTriedContext = (): [
boolean, React.Dispatch<React.SetStateAction<boolean>>,
] => {
const triedAlready = React.useContext(ErrorTriedAlreadyContext);
const setTriedAlready = React.useContext(ErrorTriedAlreadySetContext);
if (!setTriedAlready) {
throw new Error('The ErrorTriedAlreadyProvider is missing.');
}
return [triedAlready, setTriedAlready];
};
然后,我创建了一个新的 ErrorBoundary:
const ErrorBoundaryAuth: React.VFC<MyProps> = ({ children }: MyProps) => {
const { reset } = useQueryErrorResetBoundary();
return (
<ErrorTriedAlreadyContextProvider>
<ErrorBoundary
onReset={reset}
fallbackRender={({ resetErrorBoundary }) => (
<PageError
errorMessage={t({ id: 'error.auth_router_query_error', message: 'Temporary app error' })}
allowAuthReset
resetErrorBoundary={resetErrorBoundary}
/>
)}
>
<Suspense fallback={<LoadingApp />}>
{ children }
</Suspense>
</ErrorBoundary>
</ErrorTriedAlreadyContextProvider>
);
};
我将它添加到我的路由器中:
return (
<ErrorBoundaryAuth>
<PracticeTypeContextProvider>
<IonReactRouter>
还有我的按钮:
const ButtonInvalidateAuthQueries: React.VFC<MyProps> = (
{ resetErrorBoundary }: MyProps,
) => {
const queryClient = useQueryClient();
const [, setAlreadyTried] = useErrorAlreadyTriedContext();
return (
<>
<IonButton
onClick={() => {
queryClient.invalidateQueries(queryKeyInvalidateAllNonCookieQueries);
if (resetErrorBoundary) {
resetErrorBoundary();
}
// The context may not always be available.
if (setAlreadyTried) {
setAlreadyTried(true);
}
}}
>
<IonIcon icon={sync} slot="start" />
<Trans id="button.reset_queries">Try again</Trans>
</IonButton>
</>
);
};
现在可以使用了!
我正在尝试改进使用 react-query
.
我有一个 <ErrorBoundary>
组件用于我的认证用户路由器:
<ErrorBoundaryRoot errorMessage="auth error">
<PracticeTypeContextProvider>
<IonReactRouter>
... myRoutesAndStuff
组件包装器如下所示:
import { ErrorBoundary } from 'react-error-boundary';
const ErrorBoundaryRoot: React.VFC<MyProps> = ({ children, errorMessage }: MyProps) => {
const { reset } = useQueryErrorResetBoundary();
return (
<ErrorBoundary
onReset={reset}
fallbackRender={({ resetErrorBoundary }) => (
<PageError errorMessage={errorMessage} resetErrorBoundary={resetErrorBoundary} />
)}
>
<Suspense fallback={<LoadingApp />}>
{ children }
</Suspense>
</ErrorBoundary>
);
};
这正确地捕获了错误,太棒了!
现在,我想向用户显示两个按钮之一:
- 如果是第一次看到错误边界,
<ButtonInvalidateAuthQueries>
,这样我就可以尝试在不注销用户的情况下重置应用程序。 - 但是,如果失败了,我想向用户显示一个不同的按钮,
<ButtonResetApp>
,它会重置所有查询并注销用户。
我这样设置 <PageError>
组件:
const PageError: React.VFC<MyProps> = ({ errorMessage, resetErrorBoundary }: MyProps) => {
const [alreadyTriedItOnce, setAlreadyTriedItOnce] = useState(false);
return (
<IonPage>
<IonContent>
<>
{ alreadyTriedItOnce ? (
<ButtonResetApp platform={platform} resetErrorBoundary={resetErrorBoundary} />
) : (
<ButtonInvalidateAuthQueries
setAlreadyTriedItOnce={setAlreadyTriedItOnce}
resetErrorBoundary={resetErrorBoundary}
/>
)}
</>
)}
</IonContent>
</IonPage>
);
};
以及无效按钮:
const ButtonInvalidateAuthQueries: React.VFC<MyProps> = (
{ setAlreadyTriedItOnce, resetErrorBoundary }: MyProps,
) => {
const queryClient = useQueryClient();
return (
<IonButton
onClick={() => {
queryClient.invalidateQueries(queryKeyInvalidateAllAuthQueries);
resetErrorBoundary();
setAlreadyTriedItOnce(true);
}}
>
Reset auth queries
</IonButton>
);
};
但是,这不起作用,因为当用户单击 <ButtonInvalidateAuthQueries>
时,所有的 auth 组件都会重新呈现,包括错误边界,因此如果再次显示错误边界,alreadyTriedItOnce
状态初始化为false。
所以我的问题是:是否有一些聪明的方法来判断 <ButtonInvalidateAuthQueries>
是否失败,如果是,则显示 <ButtonResetApp>
?
作为一种变通方法,我可以同时显示两个按钮,并向用户说明“先单击这个,如果不起作用,再单击另一个”,但我觉得作为一名程序员,这是我的工作在任何给定时间为用户提供最佳选择。
我根据 Bergi 的评论使用 Context 解决了这个问题。
首先,我创建了一个简单的上下文来存储布尔值 useState:
import React from 'react';
import useStateBoolean from '../utils/hooks/useStateBoolean';
const ErrorTriedAlreadyContext = React.createContext(false);
const ErrorTriedAlreadySetContext = React.createContext(
{} as React.Dispatch<React.SetStateAction<boolean>>,
);
interface MyProps {
children: JSX.Element,
}
export const ErrorTriedAlreadyContextProvider: React.VFC<MyProps> = ({ children }: MyProps) => {
const [triedAlready, setTriedAlready] = useStateBoolean();
return (
<ErrorTriedAlreadyContext.Provider value={triedAlready}>
<ErrorTriedAlreadySetContext.Provider value={setTriedAlready}>
{children}
</ErrorTriedAlreadySetContext.Provider>
</ErrorTriedAlreadyContext.Provider>
);
};
export const useErrorAlreadyTriedContext = (): [
boolean, React.Dispatch<React.SetStateAction<boolean>>,
] => {
const triedAlready = React.useContext(ErrorTriedAlreadyContext);
const setTriedAlready = React.useContext(ErrorTriedAlreadySetContext);
if (!setTriedAlready) {
throw new Error('The ErrorTriedAlreadyProvider is missing.');
}
return [triedAlready, setTriedAlready];
};
然后,我创建了一个新的 ErrorBoundary:
const ErrorBoundaryAuth: React.VFC<MyProps> = ({ children }: MyProps) => {
const { reset } = useQueryErrorResetBoundary();
return (
<ErrorTriedAlreadyContextProvider>
<ErrorBoundary
onReset={reset}
fallbackRender={({ resetErrorBoundary }) => (
<PageError
errorMessage={t({ id: 'error.auth_router_query_error', message: 'Temporary app error' })}
allowAuthReset
resetErrorBoundary={resetErrorBoundary}
/>
)}
>
<Suspense fallback={<LoadingApp />}>
{ children }
</Suspense>
</ErrorBoundary>
</ErrorTriedAlreadyContextProvider>
);
};
我将它添加到我的路由器中:
return (
<ErrorBoundaryAuth>
<PracticeTypeContextProvider>
<IonReactRouter>
还有我的按钮:
const ButtonInvalidateAuthQueries: React.VFC<MyProps> = (
{ resetErrorBoundary }: MyProps,
) => {
const queryClient = useQueryClient();
const [, setAlreadyTried] = useErrorAlreadyTriedContext();
return (
<>
<IonButton
onClick={() => {
queryClient.invalidateQueries(queryKeyInvalidateAllNonCookieQueries);
if (resetErrorBoundary) {
resetErrorBoundary();
}
// The context may not always be available.
if (setAlreadyTried) {
setAlreadyTried(true);
}
}}
>
<IonIcon icon={sync} slot="start" />
<Trans id="button.reset_queries">Try again</Trans>
</IonButton>
</>
);
};
现在可以使用了!