diff --git a/src/features/routing/constants/routesConfig.tsx b/src/features/routing/constants/routesConfig.tsx index b06e6ad2..f23e7e86 100644 --- a/src/features/routing/constants/routesConfig.tsx +++ b/src/features/routing/constants/routesConfig.tsx @@ -16,19 +16,27 @@ import AuthContainer from '@/features/auth/components/AuthContainer/AuthContaine import Layout from '@/features/shared/components/Layout/Layout'; import { Footer, Navbar } from '@/features/shared'; import { Path } from '../models/Paths'; +import lazyWithRetry from '../utils/lazyWithRetry'; -const MarketView = lazy( +const MarketView = lazyWithRetry( () => import(/* webpackChunkName: "MarketView" */ '../../../views/MarketView'), + 'MarketView', ); -const LoginView = lazy( +const LoginView = lazyWithRetry( () => import(/* webpackChunkName: "LoginView" */ '../../../views/LoginView'), + 'LoginView', ); -const PortfolioView = lazy( +const PortfolioView = lazyWithRetry( () => import(/* webpackChunkName: "PortfolioView" */ '../../../views/PortfolioView'), + 'PortfolioView', ); -const Page404 = lazy(() => import(/* webpackChunkName: "Page404" */ '../../../views/404')); -const RegisterView = lazy( +const Page404 = lazyWithRetry( + () => import(/* webpackChunkName: "Page404" */ '../../../views/404'), + 'Page404', +); +const RegisterView = lazyWithRetry( () => import(/* webpackChunkName: "RegisterView" */ '../../../views/RegisterView'), + 'RegisterView', ); /** diff --git a/src/features/routing/utils/lazyWithRetry.ts b/src/features/routing/utils/lazyWithRetry.ts new file mode 100644 index 00000000..69f3f9fd --- /dev/null +++ b/src/features/routing/utils/lazyWithRetry.ts @@ -0,0 +1,39 @@ +/** + * @file Lazy load component with page reload on error. + * It helps to avoid the situation when the user is stuck on the page with a broken chunk. + */ + +import { lazy } from 'react'; +import { SessionStorageAPI } from '@/packages/sessionStorageAdapter'; + +type ComponentImportType = () => Promise<{ default: React.ComponentType }>; + +const sessionKey = 'lazyWithRetry'; + +/** + * Lazy load component with page reload on error. + * + * @param componentImport - function that returns a promise with a component. + * @param name - name of the component. + * @returns - lazy loaded component. + */ +const lazyWithRetry = (componentImport: ComponentImportType, name: string) => { + return lazy(async () => { + const hasRefreshed = SessionStorageAPI.getItem(`${sessionKey}-${name}`) || 'false'; + + try { + SessionStorageAPI.setItem(`${sessionKey}-${name}`, 'false'); + return await componentImport(); + } catch (error) { + if (hasRefreshed === 'false') { + SessionStorageAPI.setItem(`${sessionKey}-${name}`, 'true'); + window.location.reload(); + } + + if (hasRefreshed === 'true') throw new Error('chunkLoadError', error); + } + return await componentImport(); + }); +}; + +export default lazyWithRetry; diff --git a/src/packages/sessionStorageAdapter/SessionStorage.ts b/src/packages/sessionStorageAdapter/SessionStorage.ts new file mode 100644 index 00000000..07172ddb --- /dev/null +++ b/src/packages/sessionStorageAdapter/SessionStorage.ts @@ -0,0 +1,59 @@ +/** + * @file Session storage API. + * @copyright Yury Korotovskikh + */ + +import type { SessionStorageKey } from './SessionStorageKey'; + +/** + * Session storage API. + */ +class SessionStorage { + /** + * Get sesstionStorage item. + * + * @param ItemKey - Key. + * @returns Item value. + */ + public getItem(ItemKey: SessionStorageKey): T | undefined { + try { + const serialisedValue = sessionStorage.getItem(ItemKey); + if (serialisedValue === null) { + return undefined; + } + return JSON.parse(serialisedValue); + } catch { + return undefined; + } + } + + /** + * Set sessionStorage item. + * + * @param ItemKey - Key. + * @param ItemValue - Value. + */ + public setItem(ItemKey: SessionStorageKey, ItemValue: T): void { + try { + const serialisedValue = JSON.stringify(ItemValue); + sessionStorage.setItem(ItemKey, serialisedValue); + } catch { + // Do nothing + } + } + + /** + * Remove item from sessionStorage. + * + * @param ItemKey - Key. + */ + public removeItem(ItemKey: SessionStorageKey): void { + try { + sessionStorage.removeItem(ItemKey); + } catch { + // Do nothing + } + } +} + +export const SessionStorageAPI = new SessionStorage(); diff --git a/src/packages/sessionStorageAdapter/SessionStorageKey.ts b/src/packages/sessionStorageAdapter/SessionStorageKey.ts new file mode 100644 index 00000000..5a80e810 --- /dev/null +++ b/src/packages/sessionStorageAdapter/SessionStorageKey.ts @@ -0,0 +1,9 @@ +/** + * @file Type declaration. + * @copyright Yury Korotovskikh + */ + +/** + * Session storage avialiable keys. + */ +export type SessionStorageKey = `lazyWithRetry-${string}`; diff --git a/src/packages/sessionStorageAdapter/index.ts b/src/packages/sessionStorageAdapter/index.ts new file mode 100644 index 00000000..e1458f74 --- /dev/null +++ b/src/packages/sessionStorageAdapter/index.ts @@ -0,0 +1,7 @@ +/** + * @file Index. + * @copyright Yury Korotovskikh + */ + +export { SessionStorageAPI } from './SessionStorage'; +export type { SessionStorageKey } from './SessionStorageKey';