78

Is there a way to force a React-Router <Link> to load a page from path, even when the current location is already that page? I can't seem to find any mention of this in the react-router documentations.

We have a page on a route for "apply" that loads up a landing page with a hero image, some explanatory text, etc., and an "apply for this program" button that swaps in content that acts as an application form. This all happens on the same "apply" route, because users should not be able to directly navigate to this form without first hitting the landing page.

However, when they have this form open, and they click on the apply link in the nav menu again, the entire page should reload as it would on first mount, getting them "back" (but really, forward) to the landing page again.

Instead, clicking the <Link> does nothing, because react-router sees we're already on the "apply" page, and so does not unmount the current page to then mount a different one.

Is there a way to force it to unmount the current page before then mounting the requested page, even if it's for the page users are supposedly already on? (via a <Link> property for instance?)


Note: this question was posted when React-Router meant v5, and while the problem in this post is independent of a specific React-Router versions, but the solutions are not. As such, the accepted answer is the solution for React-Router v6, so if you're still using v5, first and foremost upgrade your version of React-Router, but if you absolutely can't, the accepted answer won't work for you and you'll want this answer instead.

Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153

17 Answers17

51

In the Route component, specify a random key.

<Route path={YOURPATH} render={(props) => <YourComp {...props} keyProp={someValue} key={randomGen()}/>} />

when react see a different key, they will trigger rerender.

Peiti Li
  • 4,634
  • 9
  • 40
  • 57
  • 3
    While I like the idea behind this, your answer is far from complete, and needs an explanation still of how to trigger a change to that key in a way that actually triggers a re-render as well. – Mike 'Pomax' Kamermans Jun 27 '18 at 15:32
  • 1
    i find this actually a great idea! easy to implement and works perfectly – devplayer Jan 20 '19 at 10:10
  • 2
    I used `Math.random` to generate a key. Basically what's happening is when you click the link, react router evaluates your arrow function to get the component, and by passing a new key each time using random you ensure that each link click is treated as a prop change which react will rerender. – JacksonHaenchen Sep 12 '19 at 17:43
  • I was already passing a unique slug to my route component, so I just passed it as a `key` property as well and that fixed it. Thanks! – ashastral Nov 16 '19 at 23:25
  • 2
    your solution give me invariant Violation: Maximum update depth exceeded. i dont think it's a good idea for a complex component – stackdave Dec 14 '19 at 14:40
  • A few years late to reply, but: this was for `` elements, so without links to the official docs that explain why this works for the code you're showing here, this might be a clever thing to do in general, but not strictly an _answer_ to the question as posted? – Mike 'Pomax' Kamermans Jun 23 '20 at 17:17
  • 1
    YourComp will lose it's state after re-render (which is not always triggered by navigation). It could have unwanted side effects like losing scroll position. It's better to update the key only when navigation has occurred. – Dmitry Druganov Oct 30 '20 at 23:58
  • This solution causes downstream components to be reinitialized whenever upstream state changes and not just when clicking a link. – David Sherret Nov 19 '20 at 04:18
  • Thank you, worked like a charm. I used uuid() from https://github.com/uuidjs/uuid. – Kirill Kazoolin Dec 02 '20 at 16:04
  • genius !! thank you for this!! to get an unique id why not use "new Date().getTime()" you will get an integer in milliseconds. – Cyrus Zei Dec 02 '20 at 18:08
  • 2
    this approach seems hacky – conor909 Mar 29 '22 at 16:02
25

A fix I used to solve my little need around this was to change the location that React-Router looks at. If it sees a location that we're already on (as in your example) it won't do anything, but by using a location object and changing that, rather than using a plain string path, React-Router will "navigate" to the new location, even if the path looks the same.

You can do this by setting a key that's different from the current key (similar to how React's render relies on key) with a state property that allows you to write clear code around what you wanted to do:

render() {
  const linkTarget = {
    pathname: "/page",
    key: uuid(), // we could use Math.random, but that's not guaranteed unique.
    state: {
      applied: true
    }
  };

  return (
    ...
    <Link to={linkTarget}>Page</Link>
    ...
  );
}

Note that (confusingly) you tell the Link which values you need pass as a state object, but the link will pass those values on into the component as props. So don't make the mistake of trying to access this.state in the target component!

We can then check for this in the target component's componentDidUpdate like so:

componentDidUpdate(prevProps, prevState, snapshot) {
  // Check to see if the "applied" flag got changed (NOT just "set")
  if (this.props.location.state.applied && !prevProps.location.state.applied) {
    // Do stuff here 
  }
}
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
flash
  • 1,663
  • 16
  • 16
  • 1
    Note that as of React 16 the `componentWillReceiveProps` lifecycle function is deprecated, and will be removed in React 17. I've updated your answer, but also added the bits that actually explain why this works, with the addition of `key` because that's the part that reliably makes things work. Just adding a `state` does yield a new `location`, but now we can't "reload" the same path + state anymore without changing something else. So exactly for this reason, that "something else" is the `key` attribute of a location =) – Mike 'Pomax' Kamermans Jun 23 '20 at 17:45
  • @Mike'Pomax'Kamermans what's the uuid in this case? – NFL Oct 07 '20 at 19:14
  • anything that is a valid [uuid](https://en.wikipedia.org/wiki/Universally_unique_identifier), like a global BigInteger, https://www.npmjs.com/package/uuid, etc. etc. – Mike 'Pomax' Kamermans Oct 07 '20 at 19:46
23

Based on official documentation for 'react-router' v6 for Link component

A is an element that lets the user navigate to another page by clicking or tapping on it. In react-router-dom, a renders an accessible element with a real href that points to the resource it's linking to. This means that things like right-clicking a work as you'd expect. You can use to skip client side routing and let the browser handle the transition normally (as if it were an ).

So you can pass reloadDocument to your <Link/> component and it will always refresh the page.

Example

<Link reloadDocument to={linkTo}> myapp.com </Link>

At least works for me!

Arick Vigas
  • 246
  • 2
  • 5
14

Simple as:

<Route path="/my/path" render={(props) => <MyComp {...props} key={Date.now()}/>} />

Works fine for me. When targeting to the same path:

this.props.history.push("/my/path");

The page gets reloaded, even if I'm already at /my/path.

Mendes
  • 17,489
  • 35
  • 150
  • 263
10

Not a good solution because it forces a full page refresh and throws an error, but you can call forceUpdate() using an onClick handler like:

<Link onClick={this.forceUpdate} to={'/the-page'}>
    Click Me
</Link>

All I can say is it works. I'm stuck in a similar issue myself and hope someone else has a better answer!

React router Link not causing component to update within nested routes

Community
  • 1
  • 1
unleash.it
  • 309
  • 1
  • 2
  • 10
10

This might be a common problem and I was looking for a decent solution to have in my toolbet for next time. React-Router provides some mechanisms to know when an user tries to visit any page even the one they are already.

Reading the location.key hash, it's the perfect approach as it changes every-time the user try to navigate between any page.

componentDidUpdate (prevProps) {
    if (prevProps.location.key !== this.props.location.key) {
        this.setState({
            isFormSubmitted: false,
        })
    }
}

After setting a new state, the render method is called. In the example, I set the state to default values.

Reference: A location object is never mutated so you can use it in the lifecycle hooks to determine when navigation happens

Carlos Bensant
  • 289
  • 3
  • 7
  • 2
    The one downside here is of course that this requires having a `componentDidUpdate`, constantly running code on every single props and state update, even though this will almost _never_ be the change this code tests for. A more efficient solution would be nice. – Mike 'Pomax' Kamermans Jun 23 '20 at 17:05
5

I solved this by pushing a new route into history, then replacing that route with the current route (or the route you want to refresh). This will trigger react-router to "reload" the route without refreshing the entire page.

<Link onClick={this.reloadRoute()} to={'/route-to-refresh'}>
    Click Me
</Link>

let reloadRoute = () => {
    router.push({ pathname: '/empty' });
    router.replace({ pathname: '/route-to-refresh' });
}

React router works by using your browser history to navigate without reloading the entire page. If you force a route into the history react router will detect this and reload the route. It is important to replace the empty route so that your back button does not take you to the empty route after you push it in.

According to react-router it looks like the react router library does not support this functionality and probably never will, so you have to force the refresh in a hacky way.

Zach Taylor
  • 279
  • 4
  • 9
4

I got this working in a slightly different way that @peiti-li's answer, in react-router-dom v5.1.2, because in my case, my page got stuck in an infinite render loop after attempting their solution.

Following is what I did.

<Route
  path="/mypath"
  render={(props) => <MyComponent key={props.location.key} />}
/>

Every time a route change happens, the location.key prop changes even if the user is on the same route already. According to react-router-dom docs:

Instead of having a new React element created for you using the component prop, you can pass in a function to be called when the location matches. The render prop function has access to all the same route props (match, location and history) as the component render prop.

This means that we can use the props.location.key to obtain the changing key when a route change happens. Passing this to the component will make the component re-render every time the key changes.

Deepal
  • 1,729
  • 5
  • 24
  • 34
4

I found a simple solution.

<BrowserRouter forceRefresh />

This forces a refresh when any links are clicked on. Unfortunately, it is global, so you can't specify which links/pages to refresh only.

From the documentation:

If true the router will use full page refreshes on page navigation. You may want to use this to imitate the way a traditional server-rendered app would work with full page refreshes between page navigation.

HudsonGraeme
  • 399
  • 3
  • 10
ChrisH.
  • 85
  • 1
  • 4
  • 2
    You're really going to have to add a _lot_ more details here, because this was a question from 2016, which is a small eternity for React libraries. If your 2021 answer relies on elements introduced _years later_, explain which version of React Router this applies to, and where the docs are for it =) – Mike 'Pomax' Kamermans Jun 06 '21 at 04:05
  • I don't know the origin of this question, but this answer certainly applied to me. – Johan Jun 29 '21 at 15:28
  • `forceRefresh` is removed from version 6: https://github.com/remix-run/react-router/issues/8242 – Deniz Feb 20 '22 at 18:07
3

Here's a hacky solution that doesn't require updating any downstream components or updating a lot of routes. I really dislike it as I feel like there should be something in react-router that handles this for me.

Basically, if the link is for the current page then on click...

  1. Wait until after the current execution.
  2. Replace the history with /refresh?url=<your url to refresh>.
  3. Have your switch listen for a /refresh route, then have it redirect back to the url specified in the url query parameter.

Code

First in my link component:

function MenuLink({ to, children }) {
    const location = useLocation();
    const history = useHistory();
    const isCurrentPage = () => location.pathname === to;
    const handler = isCurrentPage() ? () => {
        setTimeout(() => {
            if (isCurrentPage()) {
                history.replace("/refresh?url=" + encodeURIComponent(to))
            }
        }, 0);
    } : undefined;

    return <Link to={to} onClick={handler}>{children}</Link>;
}

Then in my switch:

<Switch>
        <Route path="/refresh" render={() => <Redirect to={parseQueryString().url ?? "/"} />} />
        {/* ...rest of routes go here... */}
<Switch>

...where parseQueryString() is a function I wrote for getting the query parameters.

David Sherret
  • 101,669
  • 28
  • 188
  • 178
  • given that there is, and the accepted answers covers it... why this answer? – Mike 'Pomax' Kamermans Nov 19 '20 at 15:27
  • @Mike'Pomax'Kamermans the accepted answer seems to require updating the downstream component to check the state in order to know to refresh, while this answer doesn't require that. Also just providing a random key in the link's "to" object didn't work for me. Maybe I'm doing something wrong? – David Sherret Nov 19 '20 at 15:32
  • Also, I'm using react-router / react-router-dom 5.2.0. Perhaps only using a random key without updating the state doesn't work in that newer version? Or did that never work? I just tried with `Math.random().toString()` – David Sherret Nov 19 '20 at 15:37
  • ah, yes if the downstream component is inaccessible things become quite a bit harder, but it's worth prefixing this answer with that particular "why". Right now it's unclear why someone might need this solution. It's been quite a few years and I've entirely moved away from React Router (because I make web sites rather than webapps, so react-router doesn't offer anything useful anymore. Just pregenerate your pages during CI/CD and dev-watch, and you get infinitely better, and browser-extension-respecting, HTML/CSS/JS out of it) – Mike 'Pomax' Kamermans Nov 19 '20 at 15:58
3

From React-Router-v6 onwards, there is a much easier way to achieve this, with the reloadDocument Link prop:

<Link to={linkTarget} reloadDocument={true}>Page</Link>
kokyuho
  • 31
  • 4
  • Remember that your post is not just going to be read "now", but also years from now, so rather than implicit dates, let's change that to talk about as of which version this pattern work =) – Mike 'Pomax' Kamermans Jun 06 '22 at 17:28
  • Thank you for your feedback. You are totally right. I have edited my anwser – kokyuho Apr 27 '23 at 14:10
0

you can use BrowserRouter forceRefresh={true}

I use react-router-dom 5

Example :

            <BrowserRouter forceRefresh={true}>
              <Link 
                to={{pathname: '/otherPage', state: {data: data}}}>
              </Link>
            </BrowserRouter>
يعقوب
  • 1,008
  • 13
  • 14
  • 1
    Any code that requires hardcoding what device the app will be running in is a questionable solution at best, especially given that the accepted solution does _not_ require this. – Mike 'Pomax' Kamermans Oct 25 '22 at 15:11
  • Yes i'm agree with your point, for my case i need to reload my page each time i use react.Link, so the solution was force reloading by BrowserRouter. – يعقوب Oct 26 '22 at 09:37
  • You should definitely put that in your answer, not in a comment: you're solving a _different_ problem from the one originally posted =) – Mike 'Pomax' Kamermans Oct 26 '22 at 15:16
-1

Solved using the Rachita Bansal answer but with the componentDidUpdate instead componentWillReceiveProps

componentDidUpdate(prevProps) {
    if (prevProps.location.pathname !== this.props.location.pathname) { window.location.reload();
    }
}
  • you probably want to comment on their answer so they can update it instead, given that the "willupdate" function was deprecated quite a few React release ago now. – Mike 'Pomax' Kamermans Apr 27 '20 at 14:53
-1

Here's my solution for v4 (yes, it's old...)

I'm not a fan of other solutions that dip into the history api. I see react-router as providing an abstraction on top of regular browser links/history, so I'd rather change how I'm using that library.

I'm also not a fan of using a randomly generated key, since React is using that to determine what has changed (and that will fire in every render, I think).

We have a regular Link component:

<Link to='/products/new'/>

and this ^ doesn't need to change. Instead, we changed our Route from this

 <Route path="/products/new" component={Whatever} />

to this:

 <Route path="/products/new" component={Whatever} key={history.location.key} />

and that made it just work. I believe this works because history.location.key is changing whenever the user clicks on a Link component, and that forces the Route to be recreated. So now the component is getting a fresh state like we want.

Patrick
  • 1,227
  • 14
  • 17
  • But how does that meaningfully differ from https://stackoverflow.com/a/51057553/740553, aside from being less reliable? You're still changing the `` code, which is fine of course, but you're trusting `history.location.key` to be different for the same URL instead of _knowing_ that you're going to have a different key by using a proper (P)RNG. As for not liking random keys because "React is using that to determine what has changed": that was what this question asked for. This question _wants_ answers that make React reload =) – Mike 'Pomax' Kamermans Mar 30 '23 at 17:17
-2

You can use the lifecycle method - componentWillReceiveProps When you click on the link, the key of the location props is updated. So, you can do a workaround, something like below,

/**
 * @param {object} nextProps new properties
 */
    componentWillReceiveProps = (nextProps)=> {
        if (nextProps.location.pathname !== this.props.location.pathname) {
            window.location.reload();
        }
    };
Community
  • 1
  • 1
  • 2
    Why would you post this when the accepted answer, from 2015, already says this. If you see a question with an accepted answer already, don't post the same answer, find a new question to answer. – Mike 'Pomax' Kamermans Sep 21 '17 at 03:59
-2

To be honest, none of these are really "thinking React". For those that land on this question, a better alternative that accomplishes the same task is to use component state.

Set the state on the routed component to a boolean or something that you can track:

this.state = {
    isLandingPage: true // or some other tracking value
};

When you want to go to the next route, just update the state and have your render method load in the desired component.

dbarth
  • 228
  • 3
  • 6
  • 1
    This sounds very much like you didn't read the question, because this *very much* doesn't address the problem outlined there. – Mike 'Pomax' Kamermans Jul 27 '20 at 20:41
  • @Mike'Pomax'Kamermans I did read the question. I landed here because I had the same question initially. The problem is, as many have previously answered, is that react-router does not operate in this fashion. This in turn, leaves you to answer the question with alternative suggestions, and most of those here are hacks. Keeping with the true spirit of this forum, I offered another alternative that kept with the mantra "thinking React" so others that land here with the same question can see a non-hack solution. – dbarth Jul 28 '20 at 15:20
  • Except it does, and the solution is to remember to use a `key` with your route, as per the answer I accepted, which has links to the documentation that explains that. The correct use of `key` attributes is in fact a crucial part of using React in general. If just "updating a state value" worked, I wouldn't have had any reason to post this question. Obviously state values are getting updated already, the question was about creating hard navigation points (the ones your back/forward buttons work with), which state updates do not effect. – Mike 'Pomax' Kamermans Jul 28 '20 at 20:34
-3

Try just using an anchor tag a href link. Use target="_self" in the tag to force the page to rerender fully.

Andrei Suvorkov
  • 5,559
  • 5
  • 22
  • 48
RAM
  • 1
  • This is specifically about what to do in the context of a codebase that uses, and _relies on_, components for navigation, server side generation, etc. "Just use something else" cannot be part of any answer. – Mike 'Pomax' Kamermans Nov 26 '18 at 16:48