3

In my React app, after navigating from Home to a child page and then hitting the back button, React <Router> shows a mismatched location in it's internal state vs the props it's passed from history and the incorrect <Route> is displayed for the currently shown URL in the address bar

The contents of the <Route> do change; data gets updated by the location change Redux action. The displayed <Route> shows new data, but it's the wrong <Route>.

The basic structure of the app is:

<Root>                                  (custom)
  <Provider>                            (react-redux)
    <ConnectedRouter>                   (connected-react-router)
      <App>                             (custom)
        <Switch>                        (react-router-dom)
          <Route path="/" exact />      (react-router-dom)
          <Route path="/quality:q" />   (react-router-dom)

Initially we navigate to root and <Route path="/> is correctly displayed. We click a link that correctly takes us to <Route path="/quality/:q" />. From there we hit browser back, the URL changes to / but <Route path="/quality:q" /> is still displayed, but it shouldn't be anymore.

Redux devtools show this rendered tree, which I'm including a few of the key components added by those we've used explicitly.

<Root>                                  (custom)
  <Provider>                            (react-redux)
    <ConnectedRouter>                   (connected-react-router)
      <Router>                          (injected)
        <Context.Provider>              (injected)
          <App>                             (custom)
            <Switch>                        (react-router-dom)
              <Route path="/" exact />      (react-router-dom)
              <Route path="/quality:q" />   (react-router-dom)

When in this incorrect state, everything up to <ConnectedRouter> is correct. <ConnectedRouter> has a single prop which is history that has the correct location, the root.

The <Router> immediately below it is mismatched. It has a history prop that is ok, but it has a stale location in state that shows the old url.

When the bad location data is context passed to the <Route>s they get the path to check against from their location prop which match's <Router>'s stale state. As a result, the wrong <Route> is rendered.

I can't figure out what I did to cause this mismatch. It's all straightforward routes and uses <Link> components for all links from home to the child pages and back.

Has anyone seen this before and/or can point me in the direction as how to resolve this problem?

Screenshot of mismatched props and state

Samuel Neff
  • 67,422
  • 16
  • 123
  • 169
  • I am facing a similar issue where history.location is correct but location isn't , were you able to figure out the cause? – gaurav5430 Jul 08 '20 at 20:17

3 Answers3

2

I think your routing structure needs to be refactored because of how you're attempting to match against (possibly nested) URLs.

If you want /quality/:id to fall underneath /, then the Route needs to be a child of the component.

For example, the Route would be:

<Route path="/" component={Home} />

Then inside the Home component, you'll have child Routes that use match.url (route props) as a string matcher:

const Home = ({ match: { url } }) => (
 <>
  <div>Home<Home>
  <Switch>
    <Route path={`${url}quality/:q` component={Quality} />
    <Route path={`${url}some/other/route`} component={Example} />
  </Switch>
 </>
);

However, if you want the opposite, where / is independent of /quality/:p, then you'll need to use exact:

<Switch>
   <Route exact path="/" component={Home} />
   <Route exact path="/quality/:p" component={Quality} />
   <Route exact path="/some/other/route" component={Example} />
<Switch>

Working example

In this example, /home, /about and /dashboard are separate routes from each other; however, /dashboard is a parent to these child routes: /dashboard/settings and /dashboard (order is important!) and /dashboard renders a Dashboard component that also happens to be a parent to a /dashboard/profile child route! Order and exact are important to how each nested route will be rendered. I've also included a goBack example inside pages/Profile to demonstrate that connected-react-router's history maintains consistency with react-router-dom Route history routing.

demo

source


If you're following the above convention, then the only other problem would be that react-hot-loader is preventing state changes. I personally don't use RHL because I find it to be rather buggy, but to each their own. That said, I've used connected-react-router for the past 3 or 4 of my projects and I've never had props/state mismatch (other than having some shared redux data that was stale and needed to be reset before loading another component that was reusing the same data structure).

Matt Carlotta
  • 13,627
  • 3
  • 24
  • 39
  • Thank you for the response. In abbreviating the message for posting I left out the `exact` keyword on the root route but it is there in the original code. I updated the question to clarify. – Samuel Neff Jan 06 '20 at 02:09
  • If you can create a reproducible codesandbox demo (doesn't need to be fancy), then it'll be easier to help. Otherwise, it's a guessing game. – Matt Carlotta Jan 06 '20 at 02:13
  • agreed, if I could recreate I could probably track it down. I'm reverting and reapplying the changes since last known working version line by line to see what is causing this. – Samuel Neff Jan 06 '20 at 18:41
  • 1
    Another thing to consider is if you're using a service worker, then it may be caching your requests. – Matt Carlotta Jan 06 '20 at 19:32
0

If you use react-router 5+, You can pass location from upper scope into Switch. I pass from history context. It fixes the same problem in my case

0

I had a similar issue. It turned out that I had a PureComponent somewhere in my tree that was causing it. Using withRouter and passing the location into the PureComponent fixed the issue.

import { withRouter } from "react-router-dom";

const App = withRouter(({ location, children }) => (
  <IntlProvider location={location}>
    {children}
  </IntlProvider>
));

ReactDOM.render(
  <Root>
    <Provider>
      <ConnectedRouter>
        <App>
          <Switch>
            <Route path="/" exact />
            <Route path="/quality:q" />
          </Switch>
        </App>
      </ConnectedRouter>
    </Provider>
  </Root>