Forcing a React-Router <Link> to load a page, even if we're already on that page - react-router

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.

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.

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
}
}

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.

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!

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

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

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.

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.

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.

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...
Wait until after the current execution.
Replace the history with /refresh?url=<your url to refresh>.
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.

There is a much easier way now to achieve this, with the reloadDocument Link prop:
<Link to={linkTarget} reloadDocument={true}>Page</Link>

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>

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 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();
}
};

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.

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

Related

Listen to a service property inside of index.html

On every reload I'm fetching data from the database. To prevent user's from seeing the app without the data being loaded, I have a promise with a resolve.
What I want is an animation to be executed right before it's being resolved - my approach is to change a property inside of the AppInitService and as soon it's f.e. true, the angular browser animation shall be executed.
The animations takes place in index.html inside of <app-root>...</app-root>, since its content is being displayed on app init. How do I listen (subscribe) to the AppInitService's property inside of index.html? Is there a .ts file allocated to index.html?
Is there a different approach?
AppInitService:
Init(): Promise<any> {
return new Promise((resolve) => {
this.db.collection('entries').valueChanges()
.subscribe(val => {
this.casesService.data = val;
resolve(true);
});
});}
app.module.ts:
export function initializeApp1(appInitService: AppInitService) {
return (): Promise<any> => {
return appInitService.Init();
};
}
providers: [
AppInitService,
{ provide: APP_INITIALIZER, useFactory: initializeApp1, deps: [AppInitService], multi: true }
],
I created an animated logo for my website that is shown before the Angular application has been started. The logo persists until the router outlet is activated, but you can use any logic as the trigger you want.
You can see the logo by visiting the website (shameless self promotion)
https://reactgular.com/
You might have to refresh the page to see it. It appears for a very short amount of time.
The app component removes the logo here:
https://github.com/reactgular/website/blob/ebd39dda032eb2bb77b9f6ae77354445a6c90cfa/src/app/main/body/body.component.ts#L54
The index.html page displays the logo here:
https://github.com/reactgular/website/blob/ebd39dda032eb2bb77b9f6ae77354445a6c90cfa/src/index.html#L75
It's important not to add the logo inside your Angular bootstrap component. The logo will be immediately removed when Angular starts, but you want it to be delayed. So you show it outside of Angular and have to manually remove it later.
Here is a copy of the index.html file that can be re-used. You are welcome to use the HTML, but please use a different logo.
https://github.com/reactgular/media/blob/master/src/index.html
If you want to have an animation to be executed when app is loading, just write your own styles in head in index.html. You should write html for animation between <app-root>Loading....</app-root> because it will be replaced once the content is ready and gets rendered.
On the other hand, if you want to have some sort of loader inside your application, you should make "loader component" and have service that "shows/hide" that loader.

React - Set property of a component from another Component

I am complete noob to react so please bear with me.
I am creating a webpage that has dusting on react components on it. I am not creating the whole page using react and this isn't an option.
What i want I want to do is load a json file and use the data to set the properties of the components on the page. These properties won't be changing once the page has rendered.
I have managed to load the json and set the properties of a component. I also need to reuse some of the properties of this component in other components that are not a children of the first component.
I suppose very basically I need to create a global set of data that can be used by any component on the page. I'm coming from an angular background so maybe this is throwing me off course a bit
Here is what I have so far
var ReleaseList=React.createClass({
getInitialState: function(){
return {
allReleases:[]
}
},
showResults :function(ReleaseData){
this.setState({
allReleases :ReleaseData
});
},
getReleases:function(){
fetch('http://imac.local:5757/s0dj8a.json').then(function(response) {
// Convert to JSON
return response.json();
}).then(function(j) {
var ReleaseData=j.releaseDetail[0]
this.showResults(ReleaseData)
}.bind(this));
},
componentDidMount(){
this.getReleases();
},
render:function(){
return (
<h1>{this.state.allReleases.title}</h1>
)
}
});
var ReleaseDataTitle=React.createClass({
render:function(){
return (
<div>
<h2>{this.props.title}</h2>
// This needs to be the same as this.state.allReleases.title in the previous component
</div>
)
}
});
ReactDOM.render(<ReleaseList/>, document.getElementById('content'));
ReactDOM.render(<ReleaseDataTitle title={'I need this to be the same as this.state.allReleases.title in <ReleaseList />'}/>, document.getElementById('banner'));
I wouldn't consider React to render small static parts of your website just as I wouldn't use jQuery if I know JS well - it's unnecessary download for such a simple task. I would however consider Preact, a 3kb React clone that was made for these kind of situations: small parts of site / app with similar API.
But here's how I would do it with React (I imagine Preact is very similar).
These properties won't be changing once the page has rendered.
If the data doesn't change, you don't even need to store the data to global variable, just render React components while passing the fetched data as props.
fetch('http://imac.local:5757/s0dj8a.json').then(function(response) {
return response.json()
}).then(function(data) {
ReactDOM.render(<First data={data} />, document.getElementById('first'))
ReactDOM.render(<Second data={data} />, document.getElementById('second'))
// And any other React component you wish to add to DOM
})
Now the data is available in each of these components as this.props.data. Now you can do whatever you want with this data: format, change, pass to children, render etc.
PS! Me and even React creators recommend to use ES6 classes, especially if you just started ..
The key is something you've already mentioned in your question. You need a global state where you can store the data required by your components. You will also need some methods to do the data distribution.
For the sake of easy maintenance, predictability and reliability of our apps, I believe React components should be agnostic about data (which means they should only represent the View layer, and shouldn't handle API calls or data processing by themselves).
Since your website is not completely built with react, you can delegate vanilla javascript methods for data fetch and pass it as a props to your React components. If you ever plan to fully migrate to React, you can make use of Redux, MobX, etc. to maintain your global state.

History replace in componentWillMount

Given the following as a mixin for a Authenticated component:
componentWillMount() {
const { authenticated, location: nextLocation } = this.props
const { replace } = this.context.router
if (!authenticated)
replace({ pathname: '/login', state: { nextLocation } })
}
When server side rendering is performed I would expect the history to transition to /login, however this is not the case and no change occurs. This works fine in the browser.
Is this beyond what react-router and/or history is capable of achieving?
What is the correct way of performing a component level redirect for server rendering?
So far I haven't found a great way of doing this. If you have a <Redirect /> or <IndexRedirect /> then match will return this via the redirectLocation param. But sometimes you need to take into account logic that simple routing can't manage, such as your isAuthenticated example.
Maybe you could do it via the redux store – if you want to redirect then dispatch some action that says where to redirect in your redux store. Then after calling loadOnServer, check the store and if there's a redirect set then do it... You could probably make a nice container component that wraps this up into some kind of context function call.

React-Router - componentDidMount not called when navigating to URL

I'm a little stumped on this one. I defined a route called classes/:id. When navigating to that route in the app, the componentDidMount() is called. However, when reloading the page or copying and pasting the URL the page completely loads, but the componentDidMount() method is not called at all.
From what I have read, the reason is because the same component mounted even though the page is being reloaded which is why the lifecycle method does ever get fired off.
What are some ways to handle this? Your help is greatly appreciated. Thanks!
componentDidMount will not be called again if you are already at a classes/:id route. You'll probably want to do something along the lines of:
componentDidUpdate(prevProps) {
if (this.props.id !== prevProps.id) {
// fetch or other component tasks necessary for rendering
}
}
I try to avoid any mixins (see willTransitionTo), as they are considered harmful.
Although componentDidMount does not fire when changing routes for the same component, componentWillUpdate and componentWillReceiveProps do.
From there, you can detect parameter changes, and can fire your actions accordingly.
the componentWillReceiveProps,componentDidUpdate lifecycle will receive the new props and then setState to change state to trigger render method.
I just dealt with this exact issue, took me a while to figure out the problem. I'm sure everything previously suggested works great as a solution. How I solved the problem was a bit different though. My componentDidMount function was essentially just providing a state update, so I just moved the functionality directly into the state and bypassed componentDidMount.
Honestly, I'm not sure if this is best practice or not (very happy for feedback if it isn't), but it's running efficiently and works great so far.

How to avoid blink/jump transitions with onEnter hook?

I have this issue when using onEnter hooks and replaceState to redirect the user to another page.
Let's say I have a page that only logged in users can see. This page/route is inside an onEnter hook, it will check if the user is logged in or not. If the user is not logged in, it is redirected to a login page (using the replaceState function). When the logged out user tries to enter the protected route, sees for an instant (on a blink) the protected page and then redirected to the login page.
How can I avoid this blink?
EDIT:
it happens when I use the callback provided by the onEnter hook.
I had encountered a similar scenario.I solved this by using the <Redirect/> component.
const Protected = () => {
<div>Protected Page</div>
}
const RenderProtected = ({ isLoggedin }) -> (
{isLoggedIn ? <Protected /> : <Redirect to="/login" />}
);
<Route exact path="/protected" component={RenderProtected} {...props} />
Not sure if this is something that most people follow. I started using the component after reading this blog by Tyler McGinnis. I just started digging around with react. Ofcourse, I store the isLoggedIn flag somewhere up in the component ladder and pass as props to the child components. I know it's too much but I'm trying not to use Flux for now..