Last active
July 30, 2024 08:46
-
-
Save raphael-leger/4d703dea6c845788ff9eb36142374bdb to your computer and use it in GitHub Desktop.
React LazyWithRetry
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { lazy } from 'react'; | |
const lazyWithRetry = (componentImport) => | |
lazy(async () => { | |
const pageHasAlreadyBeenForceRefreshed = JSON.parse( | |
window.localStorage.getItem( | |
'page-has-been-force-refreshed' | |
) || 'false' | |
); | |
try { | |
const component = await componentImport(); | |
window.localStorage.setItem( | |
'page-has-been-force-refreshed', | |
'false' | |
); | |
return component; | |
} catch (error) { | |
if (!pageHasAlreadyBeenForceRefreshed) { | |
// Assuming that the user is not on the latest version of the application. | |
// Let's refresh the page immediately. | |
window.localStorage.setItem( | |
'page-has-been-force-refreshed', | |
'true' | |
); | |
return window.location.reload(); | |
} | |
// The page has already been reloaded | |
// Assuming that user is already using the latest version of the application. | |
// Let's let the application crash and raise the error. | |
throw error; | |
} | |
}); |
This typescript version works fine for me in next:14
import { ComponentType } from "react"; const ATTEMPTS_LEFT = 2; const isSessionStorageAvailable = typeof sessionStorage !== "undefined"; const setPageWasForcedToReload = () => sessionStorage?.setItem( "page-has-been-forced-to-refresh-by-component-loader", "true", ); const setReloadedComponents = (refreshedComponents: Set<string>) => sessionStorage?.setItem( "reloaded-components", JSON.stringify(Array.from(refreshedComponents)), ); const setComponentLoaderAttemptsLeft = (attempts: number) => sessionStorage?.setItem("component-loader-attempts-left", `${attempts - 1}`); const failedToAccessSessionStorage = (error: Error) => console.error("Failed to access localStorage", error); const componentLoader = ( lazyComponent: () => Promise<{ default: ComponentType<any> }>, ): Promise<{ default: ComponentType<any> }> => { const stringifiedFunction = lazyComponent?.toString(); let reloadTimeoutId: NodeJS.Timeout | null = null; const componentsRefreshed: string[] = (isSessionStorageAvailable && JSON.parse(sessionStorage?.getItem("reloaded-components"))) || []; const sessionStorageAttemptsLeft: string = (isSessionStorageAvailable && JSON.parse(sessionStorage?.getItem("component-loader-attempts-left"))) || ATTEMPTS_LEFT; const pageHasAlreadyBeenForcedToRefresh = JSON.parse( isSessionStorageAvailable && sessionStorage?.getItem( "page-has-been-forced-to-refresh-by-component-loader", ), ) || "false"; let refreshedComponents: Set<string>; if (Array.isArray(componentsRefreshed)) refreshedComponents = new Set(componentsRefreshed); else throw Error("Unexpected value from data store"); const hasComponentRefreshed = refreshedComponents?.has(stringifiedFunction); const hasToResolveComponentLoaderPromise = hasComponentRefreshed || !pageHasAlreadyBeenForcedToRefresh; return new Promise((resolve, reject) => { lazyComponent() .then(resolve) .catch((error) => { if (hasToResolveComponentLoaderPromise) { resolve({ default: () => null }); return; } if (+sessionStorageAttemptsLeft <= 1) { try { refreshedComponents?.add(stringifiedFunction); if (isSessionStorageAvailable) { setPageWasForcedToReload(); setReloadedComponents(refreshedComponents); } } catch (error) { failedToAccessSessionStorage(error); } // capture log errors ..:sentry, slack, etc.. } try { if (isSessionStorageAvailable) setComponentLoaderAttemptsLeft(+sessionStorageAttemptsLeft); } catch (error) { failedToAccessSessionStorage(error); } if (reloadTimeoutId !== null) clearTimeout(reloadTimeoutId); reloadTimeoutId = setTimeout(() => window?.location?.reload(), 500); }); }); }; export default componentLoader;
Thanks for the code! But I think there is a little error.
const pageHasAlreadyBeenForcedToRefresh =
JSON.parse(
isSessionStorageAvailable &&
sessionStorage?.getItem(
"page-has-been-forced-to-refresh-by-component-loader",
),
) || "false";
In this part of code, if 'page-has-been-forced-to-refresh-by-component-loader' not exists you assign "false", and after that you use pageHasAlreadyBeenForcedToRefresh
as condition, but !"false"
is false.
Correction:
const pageHasAlreadyBeenForcedToRefresh =
JSON.parse(
(isSessionStorageAvailable &&
sessionStorage?.getItem(
'page-has-been-forced-to-refresh-by-component-loader'
))
) || false
Then I think hasToResolveComponentLoaderPromise
should be
const hasToResolveComponentLoaderPromise = hasComponentRefreshed || pageHasAlreadyBeenForcedToRefresh
Note
For who needs a fully-tested TypeScript implementation for this, I created a package react-safe-lazy for out-of-the-box usage.
To install:
npm i react-safe-lazy
Then just replace React.lazy
with safeLazy
and you are good to go.
import { safeLazy } from 'react-safe-lazy';
const MyComponent = safeLazy(() => import('./MyComponent'));
You can also customize the retries and reloads:
import { createSafeLazy } from 'react-safe-lazy';
const safeLazy = createSafeLazy({
importRetries: 2,
forceReload: {
maxRetries: 1,
},
});
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@chrisbruford thank you very much, I didn't know that