-
-
Save raphael-leger/4d703dea6c845788ff9eb36142374bdb to your computer and use it in GitHub Desktop.
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;
@Iam-cesar if you suffix your opening code-block backticks with the language, you would enable syntax highlighting which would make this code block much easier to read 😉
like this:
```javascript
const foo = 'bar'
```
const foo = 'bar'
@Iam-cesar if you suffix your opening code-block backticks with the language, you would enable syntax highlighting which would make this code block much easier to read 😉
@chrisbruford thank you very much, I didn't know that
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,
},
});
I would think so. If not, submit an issue to the issue tracker