I have a routing setup where if only 1 param is given, i.e /:id I want the router to always redirect to /:id/overview.
For example, if the user goes to /hello, I want them to be redirected to /hello/overview.
I've tried doing this like this:
<Switch>
<Route exact path="/" component={NoParam} />
<Redirect from="/:section" to="/:section/overview" />
<Route exact path="/:section/:pageName" component={GeneralOverviewPage} />
</Switch>
This causes an infinite re-render. I'm not sure how to achieve this redirect, and would really appreciate any help. Thanks.
EDIT=======
Now trying to do it like this:
const GeneralOverviewPage: FC<RouteComponentProps<GeneralOverviewPageProps>> = (
props
) => {
console.log(props);
return !props.match.params.pageName ? (
<Redirect to={props.match.params.section + '/overview'} />
) : (
<h1>{props.match.params.pageName}</h1>
);
};
export default GeneralOverviewPage;
and
<Route path="/:section" component={GeneralOverviewPage} />
<Route path="/:section/:pageName" component={GeneralOverviewPage} />
This means that /hello is now redirecting to /hello/hello/overview....
This will Help You around for understanding!
import React from 'react';
import { Switch, Route, Redirect } from 'react-router-dom'
const App = () => {
return (
<Switch>
<Route exact path="/" render={
() => console.log("Hi")
} />
<Redirect exact from="/:section" to="/:section/overview" render={
() => console.log("Hi 1")
} />
<Route exact path="/:section/:pageName" render={
() => console.log("Hi 2")
} />
</Switch>
)
}
export default App;
Your <Redirect from="/:section" to="/:section/overview" /> must be inside GeneralOverviewPage component.
if(!pageName){
return (<Redirect from="/:section" to="/:section/overview" />);
}
Related
I've looked online and most questions uses a different version of react-router-dom than what I'm using making the answer hard to find. What I want to do is simple, let's say a user is logged in then I wouldn't want that user to access the "sign-up" page and redirect them to the "home" page.
Here's the code I'm using that isn't working.
import { useAuth } from '../contexts/AuthContext';
import "firebase/auth";
import {Route, Navigate} from 'react-router-dom'
function AuthRoute ({element: Element, ...rest}) {
const { currentUser } = useAuth()
return (
<Route
{...rest}
render={props => {
return !currentUser ? <Element {...props} /> : <Navigate to="/" />
}}
></Route>
)
}
export default AuthRoute;
Here's how it's being called in App.js
return (
<Router>
<div className = "App">
<AuthProvider>
<Routes>
<Route path="/" element={<Home/>}/>
<AuthRoute exact path = "/sign_up" element= {SignUp} />
<Route exact path="/about" element={<About/>}/>
<Route exact path="/login" element={<SignIn/>}/>
</Routes>
</AuthProvider>
</div>
</Router>
);
It routes to sign_up but it doesn't matter if the user exists or not.
I don't know if you still need help with this but I found how to do it,
first your home route should be an exact path, then in AuthRoute() component you can do this:
export default function AuthRoute ({chidren}) {
const { currentUser } = useAuth()
if (currentUser){
return (children)
}else{
return <Navigate to='/sign_up' replace/>
}
}
then in App.js:
<AuthRoute exact path = "/sign_up" element= {SignUp} /> // should be :
<Route path='/sign_up' element={<AuthRoute> <Home/> </AuthRoute>}
</Route>
hope this can help anyone struggling with react-router v6 (like I did)
When a user completes a booking process and navigate to the confirmed details view. I need the URL to change. This is proving to be difficult to work around as the routing in done through MemoryRouter which can neither read, nor write to the URL. I need to break one of the views out and have the browser navigate to this new view.
I have tried breaking out from one router and creating a second that would return based on the original URL, then tried the very hacky window.location and direct the url to the new router.
import React from 'react';
import { MemoryRouter, Route, Switch } from 'react-router-dom';
import {
Page,
StartScreen,
StoreSearch,
ServiceSelector,
StoreSelector,
OptionSelector,
AppointmentForm,
AppointmentDetails,
ConfirmationScreen,
ErrorScreen,
} from 'components';
import { WithPageTitle, ScrollToTop } from 'containers';
import { services } from 'utilities';
const NewAppRouter = () => {
return (
<MemoryRouter>
<ScrollToTop>
<Switch>
<Route exact path="/" component={StartScreen} />
<WithPageTitle>
{pageTitle => (
<Page pageTitle={pageTitle}>
<Route path="/zip" component={StoreSearch} />
<Route path="/services" component={() => ServiceSelector({
services: services.services,
withBackButton: true,
backTo: "/zip"
})} />
<Route path="/stores" component={StoreSelector} />
<Route path="/options" component={OptionSelector} />
<Route path="/form" component={AppointmentForm} />
<Route path="/details" component={AppointmentDetails} />
{/* <Route path="/confirmation" component={ConfirmationScreen} /> */}
<Route path="/error" component={ErrorScreen} />
</Page>
)}
</WithPageTitle>
</Switch>
</ScrollToTop>
</MemoryRouter>
)
}
const AppRouter = () => {
if(window.location.href="http://localhost:9998"){
return (
<NewAppRouter />
)
} else if (window.location.href="http://localhost:9998/confirmation") {
return (
<ConfirmRouter />
)
} else {
return console.error('Route Not Found')
}
}
export default AppRouter;
Given an authentication token and a function checkToken how would I go about rerouting from multiple routes using the react router to prevent repetition like the below?
<Route exact path="/" render={() => {
return checkToken() ? (<Dashboard />) : (<Redirect to="/login" />)
}} />
<Route exact path="/about" render={() => {
return checkToken() ? (<About />) : (<Redirect to="/login" />)
}} />
It gets cumbersome if I have a couple dozen routes to have this repeated.
Surely there must be a better way!
Here is how I like to handle this:
Create a routers folder in src
Inside the router folder create 3 files AppRouter.js, PrivateRoute.js & PublicRoute.js
Here is your PublicRoute.js:
import React from 'react';
import { connect } from 'react-redux';
import { Route, Redirect } from 'react-router-dom';
export const PublicRoute = ({ isAuthenticated, component: Component, ...rest }) => (
<Route {...rest} component={(props) => (
isAuthenticated ? <Redirect to="/dashboard" /> : <Component {...props} />
)} />
);
const mapStateToProps = state => ({
isAuthenticated: // however you need to keep track of that...
});
export default connect(mapStateToProps)(PublicRoute);
Here is your PrivateRoute.js:
import React from 'react';
import { connect } from 'react-redux';
import { Route, Redirect } from 'react-router-dom';
export const PrivateRoute = ({ isAuthenticated, component: Component, ...rest }) => (
<Route {...rest} component={(props) => (
isAuthenticated ? <Component {...props} /> : <Redirect to="/" />
)} />
);
const mapStateToProps = state => ({
isAuthenticated: // however you need to keep track of that...
});
export default connect(mapStateToProps)(PrivateRoute);
And finally here is your AppRouter.js:
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Dashboard from '../components/Dashboard';
import NotFound from '../components/NotFound';
import Login from '../components/Login';
import PrivateRoute from './PrivateRoute';
import PublicRoute from './PublicRoute';
const AppRouter = () => (
<BrowserRouter>
<Switch>
{/* use PublicRoute for public routes */}
<PublicRoute exact path="/" component={Login} />
{/* use PrivateRoute for private routes */}
<PrivateRoute path="/dashboard" component={Dashboard} />
<Route component={NotFound} />
</Switch>
</BrowserRouter>
);
export default AppRouter;
For more info on HOCs (Higher Order Components) look up the docs: https://reactjs.org/docs/higher-order-components.html
One way to achieve it is by placing your checkToken function inside componentDidMount so you will always check if your user is authenticated each time this component is mounted.
After that you can do something like this:
let routes = (
<Switch>
<Route path="/login" component={LoginComponent} />
<Redirect to="/" />
</Switch>
);
if (isAuth) {
routes = (
<Switch>
<Route path="/yourRoute" component={YourComponent} />
<Redirect to="/" />
</Switch>
);
}
return (
<div>
{routes}
</div>
The logic seems simple, though I've tried a half-dozen permutations to see if anything changes. I have no idea why react-router is behaving this way:
import React from 'react'
import { View, Text } from 'react-native'
import { observer, inject } from 'mobx-react'
import { NativeRouter, Link, Route, Redirect, Switch } from 'react-router-native'
import Welcome from './welcome'
import Tutorial from './tutorial'
import Plants from './plants'
#inject('store')
#observer
class Main extends React.Component {
render() {
const newUser = true //this.props.store.plants.length === 0
const home = newUser ? '/welcome' : '/plants'
return (
<Switch>
<Route path='/plants' component={Plants} />
<Route path='/tutorial' component={Tutorial} />
<Route path='/welcome' component={Welcome} />
<Redirect to={home} />
<Route path='/' component={Welcome} />
</Switch>
)
}
}
export default Main
The final 'welcome' should be unnecessary, but I've put it there to test: if I remove the then welcome does appear, so it's clearly the that's causing a blank page to render.
This is the render() method of the top-level component:
return (
<Provider store={store}>
<NativeRouter>
<Main />
</NativeRouter>
</Provider>
)
This is based on the example at https://reacttraining.com/react-router/native/guides/philosophy which shows a Switch, Route, and Redirect all being used without an enclosing Router:
const App = () => (
<AppLayout>
<Route path="/invoices" component={Invoices}/>
</AppLayout>
)
const Invoices = () => (
<Layout>
{/* always show the nav */}
<InvoicesNav/>
<Media query={PRETTY_SMALL}>
{screenIsSmall => screenIsSmall
// small screen has no redirect
? <Switch>
<Route exact path="/invoices/dashboard" component={Dashboard}/>
<Route path="/invoices/:id" component={Invoice}/>
</Switch>
// large screen does!
: <Switch>
<Route exact path="/invoices/dashboard" component={Dashboard}/>
<Route path="/invoices/:id" component={Invoice}/>
<Redirect from="/invoices" to="/invoices/dashboard"/>
</Switch>
}
</Media>
</Layout>
)
Use the NativeRouter as the topmost component in your Main component and it will work as expected.
#inject('store')
#observer
class Main extends React.Component {
render() {
const newUser = true //this.props.store.plants.length === 0
const home = newUser ? '/welcome' : '/plants'
return (
<NativeRouter>
<Switch>
<Route path='/plants' component={Plants} />
<Route path='/tutorial' component={Tutorial} />
<Route path='/welcome' component={Welcome} />
<Redirect to={home} />
</Switch>
</NativeRouter>
)
}
}
My app is currently separated into 3 parts:
Frontend
Administration
Error
Frontend, Administration and the Error component have their own styling.
The Frontend and Administration component are also have their own Switch component to navigate through them.
The problem I am facing is that I can't hit the NoMatch path without a Redirect component. But when I do this I lose the wrong path in the browser URL.
Is there a chance when the inner Switch component has no matching route that it keeps searching in its parent Switch component?
Then I would be able to hit the NoMatch route and also keep the wrong path in the URL.
Edit: I updated my answer below with the final solution that is working like intended.
const Frontend = (props) => {
const { match } = props;
return (<div>
<h1>Frontend</h1>
<p><Link to={match.path}>Home</Link></p>
<p><Link to={`${match.path}users`}>Users</Link></p>
<p><Link to="/admin">Admin</Link></p>
<p><Link to={`${match.path}not-found-page`}>404</Link></p>
<hr />
<Switch>
<Route exact path={match.path} component={Home} />
<Route path={`${match.path}users`} component={Users} />
{
// Workaround
}
<Redirect to="/error" />
</Switch>
</div>);
};
const Admin = (props) => {
const { match } = props;
return (<div>
<h1>Admin</h1>
<p><Link to={match.path}>Dashboard</Link></p>
<p><Link to={`${match.path}/users`}>Edit Users</Link></p>
<p><Link to="/">Frontend</Link></p>
<p><Link to={`${match.path}/not-found-page`}>404</Link></p>
<hr />
<Switch>
<Route exact path={match.path} component={Home} />
<Route path={`${match.path}/users`} component={Users} />
{
// Workaround
}
<Redirect to="/error" />
</Switch>
</div>);
};
const ErrorPage = () =>
<div>
<h1>404 not found</h1>
<p><Link to="/">Home</Link></p>
</div>;
const App = () => (
<div>
<AddressBar />
<Switch>
<Route path="/error" component={ErrorPage} />
<Route path="/admin" component={Admin} />
<Route path="/" component={Frontend} />
{
// this should render the error page
// instead of redirecting to /error
}
<Route component={ErrorPage} />
</Switch>
</div>
);
Here is the final solution for this kind of requirement.
To make it work we use the location's state property. On the redirect in the inner routes we set the state to error: true.
On the GlobalErrorSwitch we check the state and render the error component.
import React, { Component } from 'react';
import { Switch, Route, Redirect, Link } from 'react-router-dom';
const Home = () => <div><h1>Home</h1></div>;
const User = () => <div><h1>User</h1></div>;
const Error = () => <div><h1>Error</h1></div>
const Frontend = props => {
console.log('Frontend');
return (
<div>
<h2>Frontend</h2>
<p><Link to="/">Root</Link></p>
<p><Link to="/user">User</Link></p>
<p><Link to="/admin">Backend</Link></p>
<p><Link to="/the-route-is-swiggity-swoute">Swiggity swooty</Link></p>
<Switch>
<Route exact path='/' component={Home}/>
<Route path='/user' component={User}/>
<Redirect to={{
state: { error: true }
}} />
</Switch>
<footer>Bottom</footer>
</div>
);
}
const Backend = props => {
console.log('Backend');
return (
<div>
<h2>Backend</h2>
<p><Link to="/admin">Root</Link></p>
<p><Link to="/admin/user">User</Link></p>
<p><Link to="/">Frontend</Link></p>
<p><Link to="/admin/the-route-is-swiggity-swoute">Swiggity swooty</Link></p>
<Switch>
<Route exact path='/admin' component={Home}/>
<Route path='/admin/user' component={User}/>
<Redirect to={{
state: { error: true }
}} />
</Switch>
<footer>Bottom</footer>
</div>
);
}
class GlobalErrorSwitch extends Component {
previousLocation = this.props.location
componentWillUpdate(nextProps) {
const { location } = this.props;
if (nextProps.history.action !== 'POP'
&& (!location.state || !location.state.error)) {
this.previousLocation = this.props.location
};
}
render() {
const { location } = this.props;
const isError = !!(
location.state &&
location.state.error &&
this.previousLocation !== location // not initial render
)
return (
<div>
{
isError
? <Route component={Error} />
: <Switch location={isError ? this.previousLocation : location}>
<Route path="/admin" component={Backend} />
<Route path="/" component={Frontend} />
</Switch>}
</div>
)
}
}
class App extends Component {
render() {
return <Route component={GlobalErrorSwitch} />
}
}
export default App;
All child component routes are wrapped in the <Switch> the parent (the switch inside the app component) you don't actually the switch in the child components.
Simply remove child switch.component and let the 404 in the <App <Switch> catch any missing.