After several years, React still doesn't support any official way of refreshing a current Route page in a way that makes the refresh instant without reloading the entire page (F5), like a user would expect from a React app. There are many use-cases (like tables that are asynchronously loaded, or forms that can be reset) where a user may want to navigate to the same page that's already loaded, as it's the de-facto way of refreshing a page similar to F5 (and can be usually also done by navigating to a different page and back), thus this workflow may be also specified as a functional requirement of many customer-facing pages, making it an impossible task for developers to correctly solve.
Specifically, I only somehow managed to implement this by modifying a <Refresh path="/refresh"/> solution from https://github.com/ReactTraining/react-router/issues/4056 this way:
<Route
path={path}
component={({ history }: RouteComponentProps) => {
history.goBack();
return null;
}}
/>
by manually adding the type reference and using the goBack that's further criticized by the open issue https://github.com/ReactTraining/history/issues/791 and then also replacing <Link> by a custom component with a following content importing { useLocation } from 'react-router-dom'; that replaces the link target if the path is supposed to stay the same:
if (useLocation().pathname === props.to.toString()) {
return <Link {...props} to="/refresh" />
}
return <Link {...props} />;
When trying to implement a workaround by adding a path like /refresh, further security and stability issues can occur, for example that the page can be navigated outside the origin domain (and the /refresh path gets included to the browser history, becoming an instance of auto-complete):
https://github.com/ReactTraining/history/issues/791
https://github.com/ReactTraining/history/issues/573
The StackOverflow solutions all seem like various workarounds, and are typically very verbose, on top of not being forward-compatible and generally hard to find and objectively assess by new JS or React users: https://github.com/ReactTraining/react-router/issues/6312
A solution like https://github.com/ReactTraining/react-router/issues/6244 also doesn't seem to work
There are many specific websites/blogs/threads trying to address this, often assuming old class-based React, but not fully working as a generic solution, often requiring coupling with the rest of the specific component implementation:
https://medium.com/@dcai900/how-to-refresh-data-when-using-react-router-link-396a2ddf8373
https://upmostly.com/tutorials/how-to-refresh-a-page-or-component-in-react
https://www.freecodecamp.org/news/force-refreshing-a-react-child-component-the-easy-way-6cdbb9e6d99c/
Some SO links to use as a reference:
https://stackoverflow.com/questions/46820682/how-do-i-reload-a-page-with-react-router/51822219
https://stackoverflow.com/questions/47602091/how-to-use-react-router-4-0-to-refresh-current-route-not-reload-the-whole-page?rq=1
Most importantly, this severely contributes towards creating a very steep learning curve of React, as the simple multi-page navigation should be something easy to implement even without any extensive knowledge. If there's any "workaround" that covers all of the possible requirements, having it included in the official documentation could be enough. Of course, this could mean anything like adding a different parameter, or even just implementing a custom method to refresh a "current component", which could be accessed via JS using onClick. I believe that a general implementation that reloads the entire content of a Route would be the most efficient way to address this, but there's obviously also the alternative of letting the user reset a specific component to its initial state directly (which includes having it invoke all hooks again).
This just isn't how React itself works. If you render the same tree with the same props, it should result in the same output and no changes to the DOM.
If you want your components to reload some external data, you need to handle that outside of Router. That's not something we can control because we don't know the internals of your component.
Quick hack:
const location = useLocation()
useEffect(() => {
fetchData()
}, [location.key])
Hi @timdorr, I'm not sure if you exactly understand the severity of this requirement for any real applications, as this isn't just a niche situation but rather a major concern, which was discussed countless of times as seen by various articles; what I'm talking about is that there's no officially supported or documented approach that can be even considered stable, even if I ignore the fact that there's a complete lack of any best practices. Many of the existing solutions are obsolete and literally don't work anymore, while forcing all React users who haven't had this issue yet to waste time trying all of the solutions, and then usually settling to one of the workarounds; is that supposed to be a correct process? You "hack" seems interesting, and it further contributes towards what I'm saying, because no other SO thread even mentioned this specific location.key approach, so anyone who isn't searching for this specific problem within GitHub issues (of react-router, because it's not related to react project) has no way of figuring this out. So in practice, for most React developers who won't find this thread, it's literally as if you didn't even give any advice.
To point out a specific example of a scenario, I'm currently trying to refactor a project which had been using a workaround of "browser refresh" due to the fact that no other solution was feasible; causing a total reload of the page including SSO redirect re-authentication taking several seconds (occurring from a user perspective "randomly" if they click the specific menu link, making the React Router approach quite useless), which is just a terrible UX, yet hacks like these are actually promoted by React community. And it's not as if the choice of this workaround was a developer mistake; because even after wasting enormous amount of time, I was unable to find any better easy fix due to false expectations of how a correct UX should be an obvious intention, wasting company & personal time budget only to realize that React doesn't even intend to address this use-case. More specifically, as I already mentioned above, this includes scenarios like being unable to re-initialize tables; even if I were able to refactor every single async loading effect and have every team member maintain the location.key workaround in every single component hook, it would still never re-initialize, as the table "already contains some content" instead of starting as empty, requiring further bloating of the code, making it hard to maintain in the future. A scenario of resetting the form contents and having it asynchronously download some fields would also require completely refactoring all of the nested components, library or not. Should I really assume that your "quick hack" is something we should build the entire project around? Should no one confirm that by officially documenting it, so that people can actually point out any flaws of the approach if it ever becomes blatantly incorrect?
The intended behaviour is very clear: by using whatever hacks, redirecting and returning back, or emptying the DOM and setting a timeout after 1ms to re-create the component; all we usually need to achieve is a simple re-initialization of the component that's a child of the Route in the same way as if the user navigated to the page again, and there should be a way to encapsulate this behaviour to apply it for example on a per-route basis. This solution needs to be forward compatible, universal and portable, while not introducing any risks of security, or breaking the navigation flow, and it should be viable in any situation to avoid unpredictable issues; so I think having a documented alternative that "always works" would be definitely preferable.
Why don't you just force the update of a component?
function useForceUpdate() {
const [state, dispatch] = useReducer((i) => i + 1, 0);
return useCallback(() => {
dispatch({});
}, []);
}
@MeiKatz Is there some reason why this should cause the component state to be entirely reset? For example if I have a Formik that should be asynchronously initialized while displaying a Backdrop, this seems to literally do nothing. Not only would it require me to manually re-initialize the entire state to simulate the same conditions and behaviour, but it also wouldn't reset the fields to their defaults, so it's far from the same result that simply "navigating to a different page and back" would achieve. Note that I posted a working solution in my first post already, so I'm not really asking for "another example of a workaround", and your example doesn't seem to be an improvement. By the way, the dispatch({}) seems to display an error as it requires 0 arguments.
@scscgit I seem to have found an easier way:
export const Page = () => {
const location = useLocation();
return <PageImpl key={location.key} />
}
So the idea is: create a wrapper around your page and make React re-create the actual page every time the location key changes. Now every time you do history.push(/same-route) the page refreshes.
@M0ns1gn0r ~Just like the previous examples of "forcing the update", this also doesn't seem to do anything at all if I click on a <Link> that points to the same page.~ Based on what documented definition of "key" should this work? As far as I know, key doesn't have the ability to completely reset a component state (and entirely re-initialize it). Isn't it the opposite, that a component which would otherwise change (its state) will stay the same if the key doesn't change? To be honest, I don't even really understand the (implications of) key definition of "Keys help React identify which items have changed, are added, or are removed.". In any case, this still seems like an attempt to do a page reload "in a React way", which is flawed by definition, because React doesn't even acknowledge the existence of this use-case (the most intuitive one that has the least development overhead and risk of errors due to code being over-complicated with redundant manual initializations) and doesn't want you to be able to trigger a component refresh. Also I don't exactly understand the last point of history.push(/same-route), where would you call this, using some onClick() event of every single link in your project?
Based on what documented definition of "key" should this work?
I don't know, I am not an expert in React internals.
Isn't it the opposite, that a component which would otherwise change (its state) will stay the same if the key doesn't change?
My understanding: yes, and no key means the component always has the same key undefined. Therefore, even though react-router re-renders the page when you visit the same route, React doesn't unmount/mount it again (which would reset its state), it keeps the existing mounted instance.
Also I don't exactly understand the last point of
history.push(/same-route), where would you call this, using some onClick() event of every single link in your project?
I meant you can programmatically force the refresh by executing history.push(/same-route). Although it's enough to click a link pointing to this same route, it will be refreshed.
See this sandbox. You can click the "Dashboard" link repeatedly and see that the displayed number changes. As soon as you remove the "key" it stops doing that.
@M0ns1gn0r Okay I'm taking back my statement, your approach does indeed seem to work. I was mistakenly trying to set the key within the output JSX of the same component on a parent element, assuming it'll reset the child element, but in order to reset the parent state I had to wrap it just like you did; and it works properly even without an explicit history.push. (For a reference, this approach of using a key is also mentioned here and here as referenced by here)
However, as said in my original comment, this still doesn't seem to be very portable as a universal solution; specifically I was thinking that this could be implemented as a re-usable HOC, which could wrap a component before passing it to <Route>'s component (which must be done outside the returning JSX), but I've had trouble writing it in a TypeScript-friendly way (there's a lack of up-to-date resources about HOC with TypeScript without classes), including some issue with useLocation (TypeError: useContext(...) is undefined), so I'd be glad for any suggestion to make this re-usable; I also have some concerns that this may need to be done after withRouter, but I'm not sure. By the way, is there no way in React to achieve this "invalidation of a parent, or a current component" by using a hook? Such a feature suggestion could address this GitHub issue (though a React's repository may be more suited for it). I mean, we should be able to simulate it by lifting a state, and then using that state as a key, while letting the child increment it using a setter/reducer. But it wouldn't be better than your solution if we had to implement a separate wrapper for every single component in order to make it work; hopefully we'll find something Router-friendly. Eventually I'd prefer to see this solved using a parameter on the level of <Route>, or even <Router>, which I think should be possible by using your approach.
@M0ns1gn0r, your approach is interesting but unfortunately did not work .
I want to keep the component state (no refresh) while clicking on the navigator back button, but the component page is always mounting and unmounting even if I click on browser back button.
https://codesandbox.io/s/react-router-basic-t6dy6?file=/example.js
@MehdiJarraya isn't your use-case the opposite of what we want to accomplish here? If you want to keep the state, then you can always save it explicitly, for example by storing it in the form of key-value pairs in a parent component, where the key would be location.key passed from the child. If the component is a singleton, you can use reactn to store a global state, and if you care about persisting the state, you can even store it in a local storage. But I don't know if there are any best practices regarding history-based state, so if there are none, I'd suggest making a separate issue to cover your use-case in the form of a feature suggestion.
@scscgit Thank you for your response. I will proceed for the moment on saving my current local state on context api until there will be a major fix to prevent component refresh while using history.goBack().
~Doesn't history.go() or history.go(0) achieve what was originally asked for?~ edit: Sorry, I read this question the wrong way. history.go() does a browser refresh, not a React component re-render.
@aureooms I couldn't get it to work that way, could you give us some example? And is it re-usable without having to hard-code any workarounds to all individual components?
You can change the key property in any React component and React will re-render it ..
@MeiKatz Yep, though there has been no example of how to do it generically, like a hook (to be called from inside) or a HOC, and preferably on the level of Router/Route in a way that doesn't bloat the entire project (because this isn't intended just as a question about a workaround for hobby projects); and this still isn't covered by any documentation (so if anyone has some user-friendly guide that covers this, please share it).
@scscgit There ist no hook because that is the way that React works! I cannot imagine a way that is easier than modifing the key prop.
@MeiKatz That's exactly why I made this issue, as you can see by the first sentence where I said that React doesn't support it :) But because this is only "relevant" for a Router, I posted it here instead of the main React repository, in order to cover a vital use-case instead of only saying that it "could theoretically be useful". I wouldn't even care if this were at least officially documented with any generic example of a usage for a Router purpose (which so many people had to cover using workaround tutorials, and whenever in a time pressure, developers just replace it by an F5 equivalent and push it to the production, harming only the end users).
In any case, the issue was uncompromisingly closed without any discussion, so all I can do is collect the information and see if this gains any traction from a community in the future.
Most helpful comment
This just isn't how React itself works. If you render the same tree with the same props, it should result in the same output and no changes to the DOM.
If you want your components to reload some external data, you need to handle that outside of Router. That's not something we can control because we don't know the internals of your component.
Quick hack: