Skip to content

Instantly share code, notes, and snippets.

@raphael-leger
Last active January 19, 2024 12:03
  • Star 38 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save raphael-leger/4d703dea6c845788ff9eb36142374bdb to your computer and use it in GitHub Desktop.
React LazyWithRetry
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;
}
});
@Markus1607
Copy link

@raphael-leger any chance you can provide the typescript version of this script?

@lisethgira
Copy link

lisethgira commented Apr 28, 2023

hola me gustaría que me ayudaras con este error ya que estoy tratando de dar una solución al chunkloaderror pero en angular

@victorlitvinenko
Copy link

victorlitvinenko commented May 10, 2023

@raphael-leger any chance you can provide the typescript version of this script?

import { ComponentType, lazy as originalLazy } from 'react';

type ImportComponent = () => Promise<{ default: ComponentType }>;

export const lazy = (importComponent: ImportComponent) =>
  originalLazy((async () => {
    const isPageHasBeenForceRefreshed = JSON.parse(
      localStorage.getItem('page-has-been-force-refreshed') || 'false',
    );

    try {
      const component = await importComponent();

      localStorage.setItem('page-has-been-force-refreshed', 'false');

      return component;
    } catch (error) {
      if (!isPageHasBeenForceRefreshed) {
        localStorage.setItem('page-has-been-force-refreshed', 'true');
        return location.reload();
      }

      throw error;
    }
  }) as ImportComponent);

@Markus1607
Copy link

@victorlitvinenko awesome thanks

@fatso83
Copy link

fatso83 commented May 12, 2023

AFAIK, the reloading approach only really works/makes sense in non-chromium browsers? From my own testing, failed module loading is persistent across manual reloads and browser restarts in chromium-derivates (Chrome, Edge, ...), and I was unable to get it to work again before manually clearing the cache.

So that's why we ended up going for the cache busting approach, as mentioned in Alon Mizrahi's article, when encountering this situation. I made a tiny library based on his approach (which parses the error message to get the module path), but improved on the cross-browser situation by finding another approach that works in Firefox (and probably all other Ecmascript engines as well). If you npm install @fatso83/retry-dynamic-import you can then use reactLazyWithRetry( () => import('./foo') ) or opt for wrapping the vanilla dynamicImportWithRetry in some other way.

@fatso83
Copy link

fatso83 commented May 12, 2023

@lisethgira Google Translate to the rescue 😄

I am not 100% sure how you do lazy loading of components in Angular, but you should essentially be able to just replace your import('./foo') with dynamicImportWithRetry( () => import('./foo'). I had a look at the Angular docs, which mention using loadChildren, so it would look something like this:

import {dynamicImportWithRetry} from '@fatso83/retry-dynamic-import';
//... other code

const routes: Routes = [
  {
    path: 'items',
    loadChildren: () => dynamicImportWithRetry(() => import('./items/items.module')).then(m => m.ItemsModule)
  }
];

@chrisbruford
Copy link

I was hitting a number of issues with the above code snippets - including:

  • Incompatible types
  • Lack of code completion on the resulting component
  • Using multiple lazyRetry functions on the same page would result in infinite loops caused by a successful component resetting the boolean for an unsuccessful component

Here's my result, incase it's useful to anyone else:

const lazyRetry = <T extends ComponentType<never>>(
  importComponent: () => Promise<{ default: T }>
): LazyExoticComponent<T> =>
  originalLazy<T>(async () => {
    const stringifiedFunction = importComponent.toString();
    const componentsRefreshed: string[] =
      JSON.parse(sessionStorage.getItem('reloaded-components')) || [];
    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);

    try {
      const component = await importComponent();
      refreshedComponents.delete(stringifiedFunction);
      sessionStorage.setItem(
        'reloaded-components',
        JSON.stringify(Array.from(refreshedComponents))
      );
      return component;
    } catch (error) {
      if (!hasComponentRefreshed) {
        refreshedComponents.add(stringifiedFunction);
        sessionStorage.setItem(
          'reloaded-components',
          JSON.stringify(Array.from(refreshedComponents))
        );
        location.reload();
      } else {
        throw error;
      }
    }
  });

export { lazyRetry };

@fatso83
Copy link

fatso83 commented Jul 21, 2023

@chrisbruford see my above comment regarding inapplicability in chromium browsers; does the forced reloading work for you? For me, the failed resolution seems persistent, and only cache busting the module will actually fix it (clearing cache also works).

From reading on the Chromium issue tracker, this is by design btw.

@chrisbruford
Copy link

@fatso83 we have some separate cache issues so I can't confirm just yet. Once they're resolved I'll be able to see if this turns up again and let you know.

@VitaliyKravetsWork
Copy link

@fatso83 we have some separate cache issues so I can't confirm just yet. Once they're resolved I'll be able to see if this turns up again and let you know.

It might be idiot question, but you don't use react laze so how it would work? and can you share import example

@fatso83
Copy link

fatso83 commented Nov 13, 2023

@VitaliyKravetsWork I am not totally sure what you are asking. I have shared code snippets showing how the library works. I suggest you check out the documentation for expanded code samples. There I show both the exports from @fatso83/retry-dynamic-import, as well as the utility wrapper in the sub-module @fatso83/retry-dynamic-import/react-lazy.

See if that makes sense, and if not, raise an issue in the issue tracker.

@VitaliyKravetsWork
Copy link

@VitaliyKravetsWork I am not totally sure what you are asking. I have shared code snippets showing how the library works. I suggest you check out the documentation for expanded code samples. There I show both the exports from @fatso83/retry-dynamic-import, as well as the utility wrapper in the sub-module @fatso83/retry-dynamic-import/react-lazy.

See if that makes sense, and if not, raise an issue in the issue tracker.

Thx for the response. I faced this issue. I tried this first code example from here and faced the infinite reload loop issue, Did I get you right your code example fix this infinite page refresh loop issue and work the same as the first code example from this topic?

@fatso83
Copy link

fatso83 commented Nov 14, 2023

I would think so. If not, submit an issue to the issue tracker

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment