Skip to content

Instantly share code, notes, and snippets.

@raphael-leger
Last active August 22, 2023 17:28
  • Star 35 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
Embed
What would you like to do?
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.

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