(Routing Issue in React) React Router creates duplicate history to the same location?

When I’m on “/home” then I click “/about” multiple times, then when I click back, it doesn’t go back to “/home”. Instead, there are many duplicate “/about” that I need to click back.

I have checked the github issue of React Router (here), but couldn’t find a clear solution, and everything seems so confusing.

Can anyone help please?

Hello!

You can add the hook shown at the bottom of the github issue page to each of your components that need to be routed or to the <Switch> (sample project):

LocationBlocker.js

import { useEffect } from "react";
import { useHistory } from "react-router-dom";

export default function useLocationBlocker() {
  const history = useHistory();
  useEffect(
    () => {
      history.block(
        (location, action) =>
          action !== "PUSH" ||
          getLocationId(location) !== getLocationId(history.location)
      );
    },
    [] // eslint-disable-line react-hooks/exhaustive-deps
  );
}

function getLocationId({ pathname, search, hash }) {
  return pathname + (search ? "?" + search : "") + (hash ? "#" + hash : "");
}

Routes.jsx

import Contact from './Contact';
import About from './About';
import useLocationBlocker from "./LocationBlocker";

export default function Routes() {
  useLocationBlocker();
  return (
    <Switch>
      <Route path="/about">
        <About />
      </Route>
      <Route path="/contact">
        <Contact />
      </Route>
      <Route path="/">
        <h2>Home</h2>
      </Route>
    </Switch>
  );
}

Then, inside your BrowserRouter:

<BrowserRouter>
  <nav>
    <Link to="/">Home</Link>
    <Link to="/about">About</Link>
  </nav>
  <Routes />
</BrowserRouter>

I’m not an expert, so there may be another, better/shorter, way of writing this code.

Anyway, I hope it helps :stuck_out_tongue:,

Regards!

2 Likes

Thanks mate for your reply! But I cannot import useHistory.
It always show this error: 'react-router-dom' does not contain an export named 'useHistory'. I’ve searched on the internet and found that the react-router-dom version 6.0.0 does not have it. But my react-router-dom is just version 4.3.1 but still it don’t work. Maybe that is a bug from the library??

Final solution:

  • Do as @skaparate told
  • But:
    • react version must be >= 16.8
    • react-router-dom must be in version 5 NOT 6 NOT 4
2 Likes

Oh, yeah! I didn’t take into account the library version; I just assumed it was the latest.

Anyway, glad you solved it :slight_smile:.

I have a small doubt @skaparate! Just to clarify, does useEffect(…) work like componentDidMount() which was only called once when the Routes component mounted.
If I do history.block(…) without using useEffect(…), is that fine? because I see it works as well.

Yes! In fact, if you wanted to use purely functional react, that’s one way to achieve similar componentDidMount functionality you get with classes.

1 Like

So do you think it is fine to do history.block(...) without useEffect(...)?

I tried it in Routes component (functional component), and when I do console.log, it only calls the function that creates Routes once . So maybe it calls history.block(...) only once too :sweat_smile: which means I may not need to useEffect()

Yes, it’s fine. Actually, if you read the last comment on the issue you posted, the proposed solution is to use history directly

Scratch that. I was wrong. I was confused with the answers :sweat_smile:.

You can use history directly as shown here:

function applyBrowserLocationBlocker(history: History) {
  let currentLocation = null

  history.block((location, action) => {
    const nextLocation = location.pathname + location.search

    if (action === 'PUSH') {
      if (currentLocation === nextLocation) {
        return false
      }
    }

    currentLocation = nextLocation
  })
}

The code of my previous answer uses this function as an effect in react.

1 Like

Thank you very much, mate!! You’ve been very helpful. I’m very grateful :partying_face: :partying_face:. It’s nice to meet you.

1 Like