react: React.lazy does not allow retrying a rejected promise
Do you want to request a feature or report a bug?
It can be seen as a feature or a bug, depending on angle. Let’s say it’s an enhancement to how lazy works.
What is the current behavior?
When using React.lazy, if the given promise rejects while trying to asynchronously load a component, it’s no longer possible to retry loading the component chunk because lazy internally caches the promise rejection.
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn’t have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:
This does not seem to work great in CodeSandbox because it’s using service workers, which get in the way when simulating offline mode, yet this small app illustrates the issue: https://codesandbox.io/s/v8921j642l
What is the expected behavior?
A promise rejection should not be cached by lazy and another attempt to render the component should call the function again, giving it the chance to return a new promise.
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
AFAIK all version of React that include lazy.
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 19
- Comments: 17 (4 by maintainers)
This issue (feature request) is still valid. I think that retrying should have first-class support.
Handling lazy loading was so much easier with use of react-loadable (unfortunately it doesn’t look like its maintained) it shame that its done so poorly in main library. Also it looks like everybody tries to do automatic retries and what about showing user some message and for example retrying on user action like clicking retry button?
Edit: I have found a way how to handle errors with retrying on user interaction if anyone needs that:
FWIW, I ran into this same problem, and found this issue while doing research as to who was caching the promise (thanks for filing it!). I found a workaround if you still have to make it work until this is properly solved here.
Codesandbox is here: https://codesandbox.io/s/1rvm45z3j3
Basically I created this function, which creates a new
React.lazycomponent whenever the import promise rejects, and assigns it back to the component variable:Another important aspect is that the error boundary of your component should be using something like render props, in order to use the new value that the variable references at any point in time (otherwise it will always use the first value assigned to
Otherand keep using it forever):Hope this helps at least make it work while this is solved!
If there was an update — it would be on this issue. 😃
You can help drive it by submitting a failing test case, with or without a fix. Here’s an example of me changing something in
React.lazya few days ago, might help: https://github.com/facebook/react/pull/14626.@threepointone that would immediately retry loading a failed module until it eventually fails and gives up, which could address part of the problem.
The part we can’t currently address unless we change
lazyimplementation is the scenario where we want to retry loading a previously failed async module load after the initial attempt(s) gave up.Imagine an app code-split by route:
Since
lazyis caching a failed promise, we can’t do the 2nd part of 2 in a timely fashion or do 4 at all right now. Changinglazyto cache only fulfilled promises and forget rejected ones would allow us to support this use case, which seems something we would like React to facilitate.This is applicable to any lazily loaded component, not only at “route-based” split points.
For anyone else coming across this issue; I’ve gone for a slightly different workaround:
This is re-rendered by a retry button in the ErrorBoundary that changes the ErrorBoundary state.
React.lazyaccepts any async function that returns a module, so you could do something like this -(I haven’t tested the above, but I think it should work?)
Sorry for bringing back an old post, but I think I’ve found a way to reproduce this. In my casse, this error only happens when redeploying. In my case i’m using Vite to bundle a React SPA with React Router and a global app-level error boundary.
Say you have a user browsing your deployed app, and you deploy a new version that changes something in a component that is being dynamically loaded. Well, when the deploy finishes, your old version of the dynamically loaded modules would no longer be available because of cache busting, but the new ones will be.
So when the user, that had the tab open with the same url all this time goes to browse another route, the module loading system will crash.
I implemented a very very hacky workaround for this like this (TS Code):