I am using Suspense
from React, and Await
from React-Router-DOM to wait for a promise and return JSX on resolve, but in most cases I want to call some functions like navigate
or something else, then the functions are called twice. I want to prevent that.
Loader
export const Loader = async ({ params }) => {
CheckAuth();
let fetch_data = fetchApi('auth/activate-account', 'POST', { verify_token: params.verify_token });
return defer({ loaderPromise: fetch_data });
}
// this loader is called in the Route loader prop
const VerifyToken = () => {
const loaderData = useLoaderData();
const navigate = useNavigate();
const { showAlert } = useGeneral();
return (
<React.Suspense fallback={<PreLoader />}>
<Await resolve={loaderData.loaderPromise}>
{(resolvedData) => {
if (resolvedData.type === "warning") {
showAlert(resolvedData.type, resolvedData.msg);
navigate('/Auth/Login', { replace: true });
return null;
}
return (
{ JSX }
);
}}
</Await>
</React.Suspense>
);
};
This causes calling the navigate
and showAlert
functions twice. I want to prevent that or use another approach or other methods.
I tried this:
let loaderRef = React.useRef(false);
<React.Suspense fallback={<PreLoader />}>
<Await resolve={loaderData.loaderPromise}>
{(resolvedData) => {
if (!loaderRef.current) {
showAlert(resolvedData.type, resolvedData.msg);
loaderRef.current = true;
setTimeout(() => {
navigate('/Auth/Login', { replace: true });
}, 0);
return null;
}
}}
</Await>
</React.Suspense>
This works but not efficient in most cases.
I suspect there is some rendering issue being exposed by a React.StrictMode
component higher up the ReactTree, specific lifecycle functions are double-invoked in non-production builds to expose logical issues in your code like unintentional side-effects.
Abstract the unintentional side-effect of showing the alert and navigating into a React component that can use the useEffect
hook to correctly issue an intentional side-effect to show the alert and conditionally redirect or render the specified JSX content.
Example:
const DataHandler = ({ children, resolvedData }) => {
const { showAlert } = useGeneral();
useEffect(() => {
if (resolvedData.type === "warning") {
showAlert(resolvedData.type, resolvedData.msg);
}
}, [resolvedData]);
if (resolvedData.type === "warning") {
return <Navigate to='/Auth/Login' replace />;
}
return children;
};
const VerifyToken = () => {
const loaderData = useLoaderData();
return (
<React.Suspense fallback={<PreLoader />}>
<Await resolve={loaderData.loaderPromise}>
{(resolvedData) => {
return (
<DataHandler resolvedData={resolvedData}>
{ JSX }
</DataHandler>
);
}}
</Await>
</React.Suspense>
);
};