Page reload if react-router-dom doesn't match any route - javascript

We are working in a project which is written in dotnet with Razor views (Pure Backend stack). Our plan to move every view to React using react-router-dom to handle routing in the client and server.
What we have in mind is to move page by page not to do a big bang because the project is already in production.
When I setup react-router-dom looks like every page is handled on the client and there is no full page reload if the route is not within the routes that Router handles.
Client Router
import { Router } from 'react-router-dom'
import { createBrowserHistory, History } from 'history'
const history: History = createBrowserHistory({ basename: baseUrl })
<Router history={history}>
...(routes and page layout)
</Router>
Server Router
<StaticRouter
basename={basename}
context={routerContext}
location={params.location.path}
>
...(routes and page layout)
</StaticRouter>
Is there any way to make the router work in this way:
If the route is within the ones handled by react-router-dom, then do client side transition
If the router is not within the ones handled by react-router-dom (which means it's still handled by dotnet backend), make a full page reload.
Let me know if you need more information.
Thank you in advance.

You need to add a NoMatch component for * route at the end of your Router
<Route component={NoMatch} />
and define your NoMatch component like below
function NoMatch() {
window.location.reload();
return null;
}
Also, your routes should be within a <Switch> because otherwise NoMatch will execute as well as your route and this will cause and endless loop.
See documentation for more details: https://reacttraining.com/react-router/web/api/Switch

Luckily we have history prop to help, which provides an action param that can indicate whether the user navigated from another route or started on the current route directly.
So this is how I solved the reload-loop issue:
/**
* Handles Legacy/SSR pages, reloads the page when the location changes
*
* #param {object} props Props
* #param {object} props.location Location object
* #param {object} props.history History object
*/
const LegacyRoute = ( { location, history } ) => {
useEffect( () => {
// Reload the page if the location change ( and not on first load )
if ( history.action === 'PUSH' ) {
window.location.reload();
}
return () => {
// Reload the page if we navigate away to another route
window.location.reload();
}
}, [ location.pathname ] );
return null;
}
Then you define the route as:
<Route component={ LegacyRoute } />

Related

React Router Dom removes the url sub paths instead of adding the routing page

I have an asp.net MVC application that serves the React app under the following url: http://domain.fake/controller/action. Here is the routing of the react app:
<Routes>
<Route path="/*" element={<Config />}>
<Route path="values" element={<ConfigKeyValues />} />
</Route>
</Routes>
When I try to navigate to the values route using the useNavigate hook:
const nav = useNavigate();
nav("values");
In the URL, instead of adding the /values path, the routing removes the whole controller/action path and only sets the /values path. So instead of getting the url http://domain.fake/controller/action/values, I'm getting http://domain.fake/values, and this is not right.
I'm able to display the component correctly, and I'm not being redirected by the server, but the url is wrong because I can't share that since it does not exist.
How can I prevent the React routing from replacing the base path but adding the new path to the url and displaying the component of that route?
To tell React Router Dom that your application is served from a subfolder, you could use the basename property, which works with createBrowserRouter as well as BrowserRouter:
The basename of the app for situations where you can't deploy to the root of the domain, but a subdirectory.
<BrowserRouter basename="/controller/action">
{/* The rest of your app goes here */}
</BrowserRouter>
createBrowserRouter(routes, {
basename: "/controller/action",
});
Side note, be aware that calling navigate("values"), without / at the beginning of "values" would take into account the path you are on and build on top of it as the doc says.
true navigate method inside react-router-dom will clear the sub path so we need to add it manually
but this is will be bad if you hard coded it
instead we will add it automatically
function YourPage() {
const nav = useNavigate()
const location = useLocation()
return (
<div>
<button onClick={() => nav(`${location.pathname}/yourTargetPath`)}>
Navigate
</button>
</div>
)
}
we will save our sub-path inside the variable and add it to navigate method
this should solve your problem

Prevent routing while user manually changes url in the browser - ReactJS

const router = createBrowserRouter([
{
path: "/",
element: <App />,
},
{
path: "/main",
element: (
<AdminAuth redirectTo="/profile">
<Main />
</AdminAuth>
),
},
])
import React from "react";
import { Link, Navigate } from "react-router-dom";
import { isAutheticated } from "../auth";
export const AdminAuth = ({ children, redirectTo }) => {
let auth = isAutheticated().user_Role;
return auth === "admin" ? children : <Navigate to={redirectTo} />;
};
I want to prevent routing while user manually changes url in the browser. I have the "/main" as an admin route, which I'm protecting, but the issue starts when user changes his role in the local storage and tries to access `"/main". I want to prevent user from manually changing the route in their url or show error if they change manually.
EDIT: I'm protecting my route in the backend, but in the frontend I don't user to even access this.
Considering that all code on the client can be changed by the user, setting up protected routes like yours, should only be for a better user experience. You cannot and should not depend on client logic to protect data from non-admin users. This has to be done on the backend/server.
Therefore, using local storage as your business logic for identifying an admin user or not, would be highly critical, since everyone could change that. Instead it should be done by some token, for exmaple JWT (Json Web Token) using a authentication provider like AWS Cognito or similiar, or even building your own server side authetication logic.
Simply by sending prop from previous page, you can control and simply send the user back to the proper page.
I want to prevent user from manually changing the route in their url
or show error if they change manually.
There's simply just no way of knowing from inside the app how exactly the URL changed in the address bar. When a user does this the browser completely reloads the page, so the entire React app is mounted fresh. react-router can then only read what the current URL is to handle route matching and rendering.
If you are certain that you don't trust the frontend client then a solution here would be to validate the user against the backend on every route/component you want to protect each time they are navigated to.
Example implementation:
import React from "react";
import { Navigate } from "react-router-dom";
import { authService } from "../auth";
export const AdminAuth = ({ children, redirectTo, role }) => {
const [userRole, setUserRole] = React.useState();
React.useEffect(() => {
authService.validateUser()
.then(user => {
setUserRole(user.user_Role);
});
}, []);
if (userRole === undefined) {
return null; // or loading indicator/spinner/etc
}
return userRole === role
? children
: <Navigate to={redirectTo} replace />;
};
const router = createBrowserRouter([
{
path: "/",
element: <App />,
},
{
path: "/main",
element: (
<AdminAuth redirectTo="/profile" role="admin">
<Main />
</AdminAuth>
),
},
]);
Note that checking with the backend for each and every route transition will incur a network cost and slow the frontend UI page transitions down. Depending on the sensitivity of the content this may be an acceptable cost. That's a decision only you or your product owners can make.

react-router v4 dispatch redux action on page change

I would like an action to be dispatched and then intercepted by the reducers on every react-router page change.
My use case is: I have the page state persisted in redux store. The state is represented as {loading, finished}. When the user clicks a button, a request is made to the server and loading becomes true. When the response comes back positive loading becomes false and finished becomes true. When the user navigates away from the page, I want the state to be reset to its initial values (loading=false, finished=false).
The following answer is great, but not work on v4 anymore because onEnter onChange and onLeave were removed.
React Router + Redux - Dispatch an async action on route change?
Edit: The best solution would be something scalable. In the future there would be multiple such pages and the state for each of them should reset on page nagivation
Thank you!
You could create your history instance separately, and listen to that and dispatch an action when something changes.
Example
// history.js
import createHistory from 'history/createBrowserHistory';
const history = createHistory();
history.listen(location => {
// Dispatch action depending on location...
});
export default history;
// App.js
import { Router, Route } from 'react-router-dom';
import Home from './components/Home';
import Login from './components/Login';
import history from './history';
class App extends React.Component {
render() {
return (
<Router history={history}>
<div>
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
</div>
</Router>
)
}
}
v4 has the location object which could be used to check if the app has navigated away from the current page.
https://reacttraining.com/react-router/web/api/location

How to use role based authentication in react-router-dom?

I have seen there are many answers for this question. But here, my question and my existing code is different. I'm using following NPM module to route in my react application.
import { Route, Switch, BrowserRouter } from 'react-router-dom';
And this is the routing file of my application.
https://gist.github.com/chanakaDe/241daafbc94df8543bced3695c7b7169
I want to use role base authentication system on this. All the roles are saved in the local storage and i need a way to check those when use goes to a route. I'm new to react. Please help me how to solve this.
Do I need to use separate file to get all the roles from local storage or can we do that inside the routing file ? Please advice
You can make Route custom components to handle the type of route. For example, if you have two Routes:
1) Admin Routes
2) Host Routes
then you will make one Component that will take the role as props and then using this component you will return the which route you need to render.
Small example is shown below:
class ResolveRoute extends React.Component {
render () {
const role = this.props.role
return (
{
() => {
if (roles === "admin") {
<Route ...this.props />
}
else if (roles === "host") {
<Route ...this.props/> // Render the routes that have host roles.
}
else {
<Redirect to="/" /> // or Not found error page.
}
}
}
)
}
}

React-Router External link

Since I'm using React Router to handle my routes in a React app, I'm curious if there is a way to redirect to an external resource.
Say someone hits:
example.com/privacy-policy
I would like it to redirect to:
example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies
I'm finding exactly zero help in avoiding writing it in plain JavaScript at my index.html loading with something like:
if (window.location.path === "privacy-policy"){
window.location = "example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies"
}
Here's a one-liner for using React Router to redirect to an external link:
<Route path='/privacy-policy' component={() => {
window.location.href = 'https://example.com/1234';
return null;
}}/>
It uses the React pure component concept to reduce the component's code to a single function that, instead of rendering anything, redirects browser to an external URL.
It works both on React Router 3 and 4.
With Link component of react-router you can do that. In the "to" prop you can specify 3 types of data:
a string: A string representation of the Link location, created by concatenating the location’s pathname, search, and hash properties.
an object: An object that can have any of the following properties:
pathname: A string representing the path to link to.
search: A string representation of query parameters.
hash: A hash to put in the URL, e.g. #a-hash.
state: State to persist to the location.
a function: A function to which current location is passed as an argument and which should return location representation as a string or as an object
For your example (external link):
https://example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies
You can do the following:
<Link to={{ pathname: "https://example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies" }} target="_blank" />
You can also pass props you’d like to be on the such as a title, id, className, etc.
There isn’t any need to use the <Link /> component from React Router.
If you want to go to external link use an anchor tag.
<a target="_blank" href="https://meetflo.zendesk.com/hc/en-us/articles/230425728-Privacy-Policies">Policies</a>
It doesn't need to request React Router. This action can be done natively and it is provided by the browser.
Just use window.location.
With React Hooks
const RedirectPage = () => {
React.useEffect(() => {
window.location.replace('https://www.google.com')
}, [])
}
With React Class Component
class RedirectPage extends React.Component {
componentDidMount(){
window.location.replace('https://www.google.com')
}
}
Also, if you want to open it in a new tab:
window.open('https://www.google.com', '_blank');
I actually ended up building my own Component, <Redirect>.
It takes information from the react-router element, so I can keep it in my routes. Such as:
<Route
path="/privacy-policy"
component={ Redirect }
loc="https://meetflo.zendesk.com/hc/en-us/articles/230425728-Privacy-Policies"
/>
Here is my component in case anyone is curious:
import React, { Component } from "react";
export class Redirect extends Component {
constructor( props ){
super();
this.state = { ...props };
}
componentWillMount(){
window.location = this.state.route.loc;
}
render(){
return (<section>Redirecting...</section>);
}
}
export default Redirect;
Note: This is with react-router: 3.0.5, it is not so simple in 4.x
I went through the same issue. I want my portfolio to redirect to social media handles. Earlier I used {Link} from "react-router-dom". That was redirecting to the sub directory as here,
Link can be used for routing web pages within a website. If we want to redirect to an external link then we should use an anchor tag. Like this,
Using some of the information here, I came up with the following component which you can use within your route declarations. It's compatible with React Router v4.
It's using TypeScript, but it should be fairly straightforward to convert to native JavaScript:
interface Props {
exact?: boolean;
link: string;
path: string;
sensitive?: boolean;
strict?: boolean;
}
const ExternalRedirect: React.FC<Props> = (props: Props) => {
const { link, ...routeProps } = props;
return (
<Route
{...routeProps}
render={() => {
window.location.replace(props.link);
return null;
}}
/>
);
};
And use with:
<ExternalRedirect
exact={true}
path={'/privacy-policy'}
link={'https://example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies'}
/>
The simplest solution is to use a render function and change the window.location.
<Route path="/goToGoogle"
render={() => window.location = "https://www.google.com"} />
If you want a small reusable component, you can just extract it like this:
const ExternalRedirect = ({ to, ...routeProps }) => {
return <Route {...routeProps} render={() => window.location = to} />;
};
and then use it (e.g. in your router switch) like this:
<Switch>
...
<ExternalRedirect exact path="/goToGoogle" to="https://www.google.com" />
</Switch>
I had luck with this:
<Route
path="/example"
component={() => {
global.window && (global.window.location.href = 'https://example.com');
return null;
}}
/>
I solved this on my own (in my web application) by adding an anchor tag and not using anything from React Router, just a plain anchor tag with a link as you can see in the picture screenshot of using anchor tag in a React app without using React Router
Basically, you are not routing your user to another page inside your app, so you must not use the internal router, but use a normal anchor.
Although this is for a non-react-native solution, but you can try.
In React Router v6, component is unavailable. Instead, now it supports element. Make a component redirecting to the external site and add it as shown.
import * as React from 'react';
import { Routes, Route } from "react-router-dom";
function App() {
return(
<Routes>
// Redirect
<Route path="/external-link" element={<External />} />
</Routes>
);
}
function External() {
window.location.href = 'https://google.com';
return null;
}
export default App;
In React Route V6 render props were removed. It should be a redirect component.
RedirectUrl:
const RedirectUrl = ({ url }) => {
useEffect(() => {
window.location.href = url;
}, [url]);
return <h5>Redirecting...</h5>;
};
Route:
<Routes>
<Route path="/redirect" element={<RedirectUrl url="https://google.com" />} />
</Routes>
I think the best solution is to just use a plain old <a> tag. Everything else seems convoluted. React Router is designed for navigation within single page applications, so using it for anything else doesn't make a whole lot of sense. Making an entire component for something that is already built into the <a> tag seems... silly?
To expand on Alan's answer, you can create a <Route/> that redirects all <Link/>'s with "to" attributes containing 'http:' or 'https:' to the correct external resource.
Below is a working example of this which can be placed directly into your <Router>.
<Route path={['/http:', '/https:']} component={props => {
window.location.replace(props.location.pathname.substr(1)) // substr(1) removes the preceding '/'
return null
}}/>
I don't think React Router provides this support. The documentation mentions
A < Redirect > sets up a redirect to another route in your application to maintain old URLs.
You could try using something like React-Redirect instead.
I was facing the same issue and solved it using by http:// or https:// in React.
Like as:
<a target="_blank" href="http://www.example.com/" title="example">See detail</a>
You can use for your dynamic URL:
<Link to={{pathname:`${link}`}}>View</Link>
For V3, although it may work for V4. Going off of Eric's answer, I needed to do a little more, like handle local development where 'http' is not present on the URL. I'm also redirecting to another application on the same server.
Added to the router file:
import RedirectOnServer from './components/RedirectOnServer';
<Route path="/somelocalpath"
component={RedirectOnServer}
target="/someexternaltargetstring like cnn.com"
/>
And the Component:
import React, { Component } from "react";
export class RedirectOnServer extends Component {
constructor(props) {
super();
// If the prefix is http or https, we add nothing
let prefix = window.location.host.startsWith("http") ? "" : "http://";
// Using host here, as I'm redirecting to another location on the same host
this.target = prefix + window.location.host + props.route.target;
}
componentDidMount() {
window.location.replace(this.target);
}
render(){
return (
<div>
<br />
<span>Redirecting to {this.target}</span>
</div>
);
}
}
export default RedirectOnServer;
I am offering an answer relevant to React Router v6 to handle dynamic routing.
I created a generic component called redirect:
export default function Redirect(params) {
window.location.replace('<Destination URL>' + "/." params.destination);
return (
<div />
)
}
I then called it in my router file:
<Route path='/wheretogo' element={<Redirect destination="wheretogo"/>}/>
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
function App() {
return (
<Router>
<Route path="/" exact>
{window.location.replace("http://agrosys.in")}
</Route>
</Router>
);
}
export default App;
Using React with TypeScript, you get an error as the function must return a React element, not void. So I did it this way using the Route render method (and using React router v4):
redirectToHomePage = (): null => {
window.location.reload();
return null;
};
<Route exact path={'/'} render={this.redirectToHomePage} />
Where you could instead also use window.location.assign(), window.location.replace(), etc.
Complementing Víctor Daniel's answer here: Link's pathname will actually take you to an external link only when there's the 'https://' or 'http://' before the link.
You can do the following:
<Link to={{ pathname:
> "https://example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies"
> }} target="_blank" />
Or if your URL doesn't come with 'https://', I'd do something like:
<Link to={{pathname:`https://${link}`}} target="_blank" />
Otherwise it will prepend the current base path, as Lorenzo Demattécommented.
If you are using server-side rending, you can use StaticRouter. With your context as props and then adding <Redirect path="/somewhere" /> component in your app. The idea is every time React Router matches a redirect component it will add something into the context you passed into the static router to let you know your path matches a redirect component.
Now that you know you hit a redirect you just need to check if that’s the redirect you are looking for. then just redirect through the server. ctx.redirect('https://example/com').
You can now link to an external site using React Link by providing an object to to with the pathname key:
<Link to={ { pathname: '//example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies' } } >
If you find that you need to use JavaScript to generate the link in a callback, you can use window.location.replace() or window.location.assign().
Over using window.location.replace(), as other good answers suggest, try using window.location.assign().
window.location.replace() will replace the location history without preserving the current page.
window.location.assign() will transition to the URL specified, but will save the previous page in the browser history, allowing proper back-button functionality.
location.replace()
location.assign()
Also, if you are using a window.location = url method as mentioned in other answers, I highly suggest switching to window.location.href = url.
There is a heavy argument about it, where many users seem to adamantly want to revert the newer object type window.location to its original implementation as string merely because they can (and they egregiously attack anyone who says otherwise), but you could theoretically interrupt other library functionality accessing the window.location object.
Check out this conversation. It's terrible.
JavaScript: Setting location.href versus location
I was able to achieve a redirect in react-router-dom using the following
<Route exact path="/" component={() => <Redirect to={{ pathname: '/YourRoute' }} />} />
For my case, I was looking for a way to redirect users whenever they visit the root URL http://myapp.com to somewhere else within the app http://myapp.com/newplace. so the above helped.

Categories

Resources