Skip to content

Instantly share code, notes, and snippets.

@raphael-leger
Last active April 17, 2024 17:51
Show Gist options
  • Star 39 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save raphael-leger/4d703dea6c845788ff9eb36142374bdb to your computer and use it in GitHub Desktop.
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;
}
});
@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

@Iam-cesar
Copy link

Iam-cesar commented Mar 15, 2024

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;

@chrisbruford
Copy link

@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
Copy link

Iam-cesar commented Mar 25, 2024

@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

@franciscoMerono
Copy link

franciscoMerono commented Apr 16, 2024

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

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