From a1b3496c1608b825426206ed35ab407b64981564 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Wed, 17 Jul 2024 14:04:09 +0200 Subject: [PATCH 1/3] fix retry import error --- src/libs/Navigation/AppNavigator/index.tsx | 5 +-- src/utils/lazyRetry.ts | 37 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/utils/lazyRetry.ts diff --git a/src/libs/Navigation/AppNavigator/index.tsx b/src/libs/Navigation/AppNavigator/index.tsx index f8b14781a5ec..787ede6c14f2 100644 --- a/src/libs/Navigation/AppNavigator/index.tsx +++ b/src/libs/Navigation/AppNavigator/index.tsx @@ -2,9 +2,10 @@ import React, {lazy, memo, Suspense, useContext, useEffect} from 'react'; import {NativeModules} from 'react-native'; import {InitialURLContext} from '@components/InitialURLContextProvider'; import Navigation from '@libs/Navigation/Navigation'; +import lazyRetry from '@src/utils/lazyRetry'; -const AuthScreens = lazy(() => import('./AuthScreens')); -const PublicScreens = lazy(() => import('./PublicScreens')); +const AuthScreens = lazy(() => lazyRetry(() => import('./AuthScreens'))); +const PublicScreens = lazy(() => lazyRetry(() => import('./PublicScreens'))); type AppNavigatorProps = { /** If we have an authToken this is true */ diff --git a/src/utils/lazyRetry.ts b/src/utils/lazyRetry.ts new file mode 100644 index 000000000000..2932d4aae5c9 --- /dev/null +++ b/src/utils/lazyRetry.ts @@ -0,0 +1,37 @@ +import type {ComponentType, LazyExoticComponent} from 'react'; + +type ComponentImport = () => Promise<{default: LazyExoticComponent}>; + +/** + * Attempts to lazily import a React component with a retry mechanism on failure. + * If the initial import fails with a `ChunkLoadError`, the function will refresh the page once and retry the import. + * If the import fails again after the refresh, the error is propagated. + * + * @param componentImport - A function that returns a promise resolving to a lazily imported React component. + * @returns A promise that resolves to the imported component or rejects with an error after a retry attempt. + */ +const lazyRetry = function (componentImport: ComponentImport): Promise<{default: LazyExoticComponent}> { + return new Promise((resolve, reject) => { + // Retrieve the retry status from sessionStorage, defaulting to 'false' if not set + const hasRefreshed: unknown = JSON.parse(sessionStorage.getItem('retry-lazy-refreshed') ?? 'false'); + + componentImport() + .then((component) => { + // Reset the retry status to 'false' on successful import + sessionStorage.setItem('retry-lazy-refreshed', 'false'); // success so reset the refresh + resolve(component); + }) + .catch((error) => { + if (!hasRefreshed) { + // Set the retry status to 'true' and refresh the page + sessionStorage.setItem('retry-lazy-refreshed', 'true'); + window.location.reload(); // Refresh the page to retry the import + } else { + // If the import fails again reject with the error to trigger default error handling + reject(error); + } + }); + }); +}; + +export default lazyRetry; From 3ffa6de38fe4fdedd4253d7c8ca2471dac3f8863 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Wed, 17 Jul 2024 15:37:38 +0200 Subject: [PATCH 2/3] fix types --- src/libs/Navigation/AppNavigator/index.tsx | 5 +++-- src/utils/lazyRetry.ts | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/index.tsx b/src/libs/Navigation/AppNavigator/index.tsx index 787ede6c14f2..a40847376667 100644 --- a/src/libs/Navigation/AppNavigator/index.tsx +++ b/src/libs/Navigation/AppNavigator/index.tsx @@ -2,10 +2,11 @@ import React, {lazy, memo, Suspense, useContext, useEffect} from 'react'; import {NativeModules} from 'react-native'; import {InitialURLContext} from '@components/InitialURLContextProvider'; import Navigation from '@libs/Navigation/Navigation'; +import type {EmptyObject} from '@src/types/utils/EmptyObject'; import lazyRetry from '@src/utils/lazyRetry'; -const AuthScreens = lazy(() => lazyRetry(() => import('./AuthScreens'))); -const PublicScreens = lazy(() => lazyRetry(() => import('./PublicScreens'))); +const AuthScreens = lazy(() => lazyRetry(() => import('./AuthScreens'))); +const PublicScreens = lazy(() => lazyRetry(() => import('./PublicScreens'))); type AppNavigatorProps = { /** If we have an authToken this is true */ diff --git a/src/utils/lazyRetry.ts b/src/utils/lazyRetry.ts index 2932d4aae5c9..a5d49bb25d95 100644 --- a/src/utils/lazyRetry.ts +++ b/src/utils/lazyRetry.ts @@ -1,6 +1,6 @@ -import type {ComponentType, LazyExoticComponent} from 'react'; +import type {ComponentType} from 'react'; -type ComponentImport = () => Promise<{default: LazyExoticComponent}>; +type ComponentImport = () => Promise<{default: ComponentType}>; /** * Attempts to lazily import a React component with a retry mechanism on failure. @@ -10,7 +10,7 @@ type ComponentImport = () => Promise<{default: LazyExoticComponent}> { +const lazyRetry = function (componentImport: ComponentImport): Promise<{default: ComponentType}> { return new Promise((resolve, reject) => { // Retrieve the retry status from sessionStorage, defaulting to 'false' if not set const hasRefreshed: unknown = JSON.parse(sessionStorage.getItem('retry-lazy-refreshed') ?? 'false'); From c3cbd78c5b49e7b0bfe160de9c091b047b115368 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Thu, 18 Jul 2024 13:03:49 +0200 Subject: [PATCH 3/3] fix comments --- src/CONST.ts | 1 + src/libs/Navigation/AppNavigator/index.tsx | 5 ++--- src/utils/lazyRetry.ts | 21 +++++++++++++-------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 3f141905e84c..8ba6a37d9862 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5140,6 +5140,7 @@ const CONST = { SESSION_STORAGE_KEYS: { INITIAL_URL: 'INITIAL_URL', ACTIVE_WORKSPACE_ID: 'ACTIVE_WORKSPACE_ID', + RETRY_LAZY_REFRESHED: 'RETRY_LAZY_REFRESHED', }, RESERVATION_TYPE: { diff --git a/src/libs/Navigation/AppNavigator/index.tsx b/src/libs/Navigation/AppNavigator/index.tsx index a40847376667..787ede6c14f2 100644 --- a/src/libs/Navigation/AppNavigator/index.tsx +++ b/src/libs/Navigation/AppNavigator/index.tsx @@ -2,11 +2,10 @@ import React, {lazy, memo, Suspense, useContext, useEffect} from 'react'; import {NativeModules} from 'react-native'; import {InitialURLContext} from '@components/InitialURLContextProvider'; import Navigation from '@libs/Navigation/Navigation'; -import type {EmptyObject} from '@src/types/utils/EmptyObject'; import lazyRetry from '@src/utils/lazyRetry'; -const AuthScreens = lazy(() => lazyRetry(() => import('./AuthScreens'))); -const PublicScreens = lazy(() => lazyRetry(() => import('./PublicScreens'))); +const AuthScreens = lazy(() => lazyRetry(() => import('./AuthScreens'))); +const PublicScreens = lazy(() => lazyRetry(() => import('./PublicScreens'))); type AppNavigatorProps = { /** If we have an authToken this is true */ diff --git a/src/utils/lazyRetry.ts b/src/utils/lazyRetry.ts index a5d49bb25d95..35c6b444e612 100644 --- a/src/utils/lazyRetry.ts +++ b/src/utils/lazyRetry.ts @@ -1,34 +1,39 @@ import type {ComponentType} from 'react'; +import CONST from '@src/CONST'; -type ComponentImport = () => Promise<{default: ComponentType}>; +type Import = Promise<{default: T}>; +type ComponentImport = () => Import; /** * Attempts to lazily import a React component with a retry mechanism on failure. - * If the initial import fails with a `ChunkLoadError`, the function will refresh the page once and retry the import. + * If the initial import fails the function will refresh the page once and retry the import. * If the import fails again after the refresh, the error is propagated. * * @param componentImport - A function that returns a promise resolving to a lazily imported React component. * @returns A promise that resolves to the imported component or rejects with an error after a retry attempt. */ -const lazyRetry = function (componentImport: ComponentImport): Promise<{default: ComponentType}> { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const lazyRetry = function >(componentImport: ComponentImport): Import { return new Promise((resolve, reject) => { // Retrieve the retry status from sessionStorage, defaulting to 'false' if not set - const hasRefreshed: unknown = JSON.parse(sessionStorage.getItem('retry-lazy-refreshed') ?? 'false'); + const hasRefreshed = JSON.parse(sessionStorage.getItem(CONST.SESSION_STORAGE_KEYS.RETRY_LAZY_REFRESHED) ?? 'false') as boolean; componentImport() .then((component) => { // Reset the retry status to 'false' on successful import - sessionStorage.setItem('retry-lazy-refreshed', 'false'); // success so reset the refresh + sessionStorage.setItem(CONST.SESSION_STORAGE_KEYS.RETRY_LAZY_REFRESHED, 'false'); // success so reset the refresh resolve(component); }) - .catch((error) => { + .catch((component: ComponentImport) => { if (!hasRefreshed) { + console.error('Failed to lazily import a React component, refreshing the page in order to retry the operation.', component); // Set the retry status to 'true' and refresh the page - sessionStorage.setItem('retry-lazy-refreshed', 'true'); + sessionStorage.setItem(CONST.SESSION_STORAGE_KEYS.RETRY_LAZY_REFRESHED, 'true'); window.location.reload(); // Refresh the page to retry the import } else { + console.error('Failed to lazily import a React component after the retry operation!', component); // If the import fails again reject with the error to trigger default error handling - reject(error); + reject(component); } }); });