react-router: does not update inside
Version
5.1.2 (not tested on previous versions)
Test Case
https://codesandbox.io/s/react-router-t0ig4
Steps to reproduce
Please see above CodeSandbox.
Expected Behavior
Using React Router with Suspense/lazy.
const Home = () => <div>I am not lazy loaded</div>;
const About = React.lazy( () => import('./About.js') );
const App = () => (
<Router>
<div>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</div>
<Suspense fallback="Loading...">
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
</Suspense>
</Router>
);
Home is a normal component, About is lazy-loaded.
When you click “About” link, “Loading…” should appear (it does).
Now, while About is still loading, if you click “Home” link, the page should update immediately and show you the “Home” page again.
Actual Behavior
In fact, the route transition back to “Home” is delayed until About has finished loading.
This is hard to spot in a dev environment, as About will load quickly anyway. So in the CodeSandbox above I’ve substituted a fake Lazy component which never loads. The result is that the page is stuck on “Loading…” forever - navigation ceases to work.
Two things solve the problem:
- Add a
useLocation()somewhere below the<Switch>element to force<Switch>to re-render on a route transition. - Remove the
<Switch>entirely. If it’s just a series of<Route>elements, it works as expected.
I have played around with this a lot, and can’t figure out if it’s a bug in React Router, or in React’s handling of Context within Suspense. But I figured I’d post this here first.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 2
- Comments: 33 (15 by maintainers)
right back at ya, creator of react router!
Same deal as on Oct 26th. This bug still remains.
Thanks, inventor of React!
I think we fixed this in https://github.com/facebook/react/pull/23095. Try testing it with the
@nextnpm tag of react/react-dom tomorrow.Yea this is fixed. https://codesandbox.io/s/react-router-forked-vj12o?file=/index.js
@ccnklc If I remember right,
<Switch>requires its children to each have apathprop, so nesting<Suspense>inside<Switch>won’t work - the<Route>s need to be directly inside<Switch>.Also, the location of
<Suspense>in the tree alters behavior. If you have multiple nested<Switch>es, you might want the Suspense boundary to enclose them all together.Just tried the original test case with React + ReactDOM 17.0.0-rc.0, and I’m afraid I can confirm the issue remains.
I’ve made a repro case without React Router https://github.com/facebook/react/issues/19701.
While this issue remains outstanding in React, might it be worthwhile implementing a workaround in React Router?
The problem is the use of
<Context.Consumer>inSwitch- using auseContext()hook solves the problem.This workaround could be implemented so it only kicks in with versions of React which support hooks. Actual implementation would be more complicated, but something along the lines of:
The point is, that as long as there is any child component of a
<Suspense />component that throws aPromiseanything inside theSuspensecomponent will be replaced by the component defined in thefallbackprop. When you load a new component inside a switch that is not loaded yet, anything inside theSuspensecomponent will be replaced, even theSwitchcomponent. Therefore you have to catch the thrown promise inside theSwitchcomponent, meaning you should do something like the following:EDIT: I looked at your code sandbox example and I think I get your point now. I am not sure if we have to change anything in the implementation but your expected behaviour seem reasonable to me.
Actually, this is the expected behaviour of
<Suspense />. You have to wrap each route inside of an own suspense component.