** Note this is in a react class component
DefaultContainer = () => {
return (
<div className="app_container">
<SideNav />
<TopBar />
<Route exact path="/" component={Home} />
<Route path="/recent" component={Recent} />
<Route path="/AddNote" component={AddNote} />
<Route path="/ToDo" component={ToDo} />
</div>
);
};
// Check for authenticaition
AuthRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render={props => {
if (this.props.isAuthenticated) {
return <Component {...props} />;
}
else {
return (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
);
}
}}
/>
);
};
render() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/login" component={this.LogInContainer} />
<Route exact path="/register" component={this.RegisterContainer} />
<this.AuthRoute component={this.DefaultContainer} />
</Switch>
</BrowserRouter>
);
}
}
I login, this.props.isAuthenticated is set to true. When I try to visit the '/' route I get redirected back to the login? But this.props.isAuthenticated is true in React Dev tools? Cannot grasp what is going wrong.
Should be as simple as this:
AuthRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render={props => {
if (props.isAuthenticated) { // <- here is the difference
return <Component {...props} />;
}
else {
return (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
);
}
}}
/>
);
};
Related
I am struggling to render my routes list with React router v6.
I have already read the docs and I am aware of a new structure of routing, but currently having a problem rendering list (nested) of routes.
export default [{
path: '/',
element: Root,
routes: [{
path: REGISTER,
element: Register,
isProtected: true,
}],
}];
export const RouteMatcher = ({ routes }) => {
return routes.map((route, index) => {
if (route.element) {
return (
<Route key={index} path={route.path} element={<route.element />}>
{route.routes ? (
<Route path={route.path} element={<RouteMatcher routes={route.routes} />
) : null}
</Route>
);
}
return null;
});
};
<BrowserRouter>
<Routes>
<Route path="/" element={<RouteMatcher routes={routes} />} />
</Routes>
</BrowserRouter>
It's not obvious to me what's going on here even though the error message has a clear explanation of the issue.
Even I try this way it doesn't work.
<BrowserRouter>
<Routes>
<Route path="/">
{routes.map((route, index) => {
if (route.element) {
return (
<Route key={index} path={route.path} element={<route.element />}>
{route.routes ? (
<Route path={route.path} element={<RouteMatcher routes={route.routes} indexPathname={indexPathname} />} />
) : null}
</Route>
);
}
return null;
})}
</Route>
</Routes>
</BrowserRouter>
As you can see Route is always a child of Routes or Route (when nested).
** UPDATED **
Here is the react router v5 implementation
<Provider store={store}>
<GlobalStyles />
<AppContentWrapper>
<RouteHandler />
</AppContentWrapper>
</Provider>
Route handler component
<BrowserRouter>
{generateRouteMatches(
routes,
indexPathname,
auth.isLoading,
auth.isLoggedIn
)}
</BrowserRouter>
Route generator component
export const generateRouteMatches = (
baseRoutes: IAppRoute[],
indexPathname: string,
userPermissions: PermissionShape[],
isLoading: boolean,
isLoggedIn: boolean,
) => {
AccessControl.createPermissions(userPermissions);
return baseRoutes.map((route) => (
<MatchRoutes
indexPathname={indexPathname}
authIsLoading={isLoading}
isLoggedIn={isLoggedIn}
key={route.path}
{...route}
/>
));
};
MatchRoutes component with RouteRenderer
function MatchRoutes({ location, ...route }: any) {
const routePermissions = AccessControl.getPermissions(route.zone);
if (!routePermissions.canRead && route.isProtectedRoute) {
return <Route {...omit(route, ['component'])} component={() => {
return <div className="centerXY">You dont have permissions to view this page</div>;
}} />;
}
return (
<Route {...omit(route, ['component'])} render={(props) => (
<RouteRenderer {...props} route={route} location={location} />
)} />
);
}
function RouteRenderer({ route, ...props }: any) {
const location = useLocation();
if (location?.pathname === '/') {
return (
<Redirect
to={{
pathname: route.indexPathname,
state: { from: location },
}}
/>
);
}
if (route.isProtectedRoute && !route.isLoggedIn && !route.authIsLoading) {
return (
<Redirect to={{
pathname: '/login',
state: { from: location },
}}/>
);
}
if (route.component) {
return (
<route.component
{...props}
params={props.match.params}
routes={route.routes}
>
{route.routes
? route.routes.map((cRoute, idx) => (
<MatchRoutes
authIsLoading={route.authIsLoading}
isLoggedIn={route.isLoggedIn}
key={idx}
{...cRoute}
/>
))
: null
}
</route.component>
);
} else if (route.routes) {
return (
<>
{route.routes.map((cRoute, idx) => (
<MatchRoutes
authIsLoading={route.authIsLoading}
isLoggedIn={route.isLoggedIn}
key={idx}
{...cRoute}
/>
))}
</>
);
} else {
return null;
}
}
export default MatchRoutes;
In the first example the RouteMatcher component is rendering a Route component directly. The mapped routes it is rendering need to be wrapped in a Routes component.
export const RouteMatcher = ({ routes }) => {
return (
<Routes>
{routes
.filter(route => route.element)
.map((route, index) => {
return (
<Route
key={index}
path={route.path}
element={<route.element />}
>
{route.routes && (
<Route
path={route.path}
element={<RouteMatcher routes={route.routes} />}
/>
)}
</Route>
);
})
}
</Routes>
);
};
I suspect something similar is occurring int he second code example as well.
I suggest using a better formed routes configuration and use the useRoutes hook.
Example:
export default [{
path: '/',
element: <Root />,
children: [
{
element: <AuthOutlet />,
children: [
{
path: REGISTER,
element: <Register />,
},
... other routes to protect ...
],
... other unprotected routes ...
},
],
}];
...
import appRoutes from '../path/to/routes';
...
const routes = useRoutes(appRoutes);
...
<BrowserRouter>
{routes}
</BrowserRouter>
I'm getting an infinite loop when trying to redirect users to wanted path after obligatory login.
My Private Route, when i add state: { from: props.location }, I get infinite loop.
const PrivateRoute = ({
component: Component,
exact,
path,
}: RouteProps): JSX.Element => {
if (!Component) return <div />;
const classes = useStyles();
const auth = useSelector((state: RootState) => state.auth);
return (
<Route
path={path}
exact={exact}
render={(props) => (auth.loading ? (
<Backdrop className={classes.backdrop} open>
<CircularProgress color="inherit" />
</Backdrop>
) : auth.isAuthenticated ? (
<Component {...props} />
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location },
}}
/>
))}
/>
);
};
My Switch
<Router history={history}>
<ThemeProvider theme={theme}>
<CssBaseline />
<Alert th={ui.darkTheme} />
<UpdateApp />
<Route path="/login" exact component={Login} />
<Switch>
<PrivateRoute path="/" exact component={Homepage} />
<PrivateRoute path="/info" exact component={Infos} />
<PrivateRoute path="/info/add" exact component={AddInfo} />
<PrivateRoute path="/info/:id" exact component={ShowInfo} />
<PrivateRoute path="/info/:id/edit" exact component={EditInfo} />
<PrivateRoute component={Four} />
</Switch>
</ThemeProvider>
</Router>
Resolved it with checking for pathname
const PrivateRoute = ({
component: Component,
exact,
path,
}: RouteProps): JSX.Element => {
if (!Component) return <div>ERROR</div>;
const classes = useStyles();
const auth = useSelector((state: RootState) => state.auth);
return (
<Route
path={path}
exact={exact}
render={(props) => (auth.loading ? (
<Backdrop className={classes.backdrop} open>
<CircularProgress color="inherit" />
</Backdrop>
) : auth.isAuthenticated ? (
<Component {...props} />
) : (props.location.pathname !== '/login') && (
<Redirect to={{
pathname: '/login',
state: { from: props.location },
}}
/>
))}
/>
);
};
I'm not sure if I'm using a HOC correctly but I have state at the top app level which needs to be updated from a child components that exists in a HOC.
my main app router that holds the state
class AppRouter extends React.Component {
state = {
selected: "",
};
updateSelected = selected => {
this.setState({ selected });
};
updateReports = reports => {
this.setState({ reports });
};
render() {
return (
<Router history={history}>
<div>
<div className="holygrail">
<Header setIsAuth={this.setIsAuth} isAuth={this.state.isAuth} />
<Switch>
<PublicRoute
path="/login"
isAuth={this.state.isAuth}
component={() => <Login setIsAuth={this.setIsAuth} />}
exact={true}
/>
<PrivateRoute
path="/"
selected={this.state.selected}
isAuth={this.state.isAuth}
updateSelected={this.updateSelected}
updateReports={this.updateReports}
component={Dashboard}
exact={true}
/>
<Route component={NotFound} />
</Switch>
</div>
</div>
</Router>
);
}
}
export default AppRouter;
I have a PrivateRoute that then has a template which include a nav that should not be shown on a PublicRoute
export const PrivateRoute = ({ component: Component, isAuth, ...rest }) => (
<Route
{...rest}
render={props => {
console.log("Private Route ", isAuth);
return isAuth ? (
<div className="holygrail-body">
<Nav
updateSelected={this.updateSelected} <-- how to pass these back up to AppRouter parent?
updateReports={this.updateReports}
/>
<Component {...props} />
</div>
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
);
}}
/>
);
export default PrivateRoute;
How do I pass the Nav props to update the main component state.
I have the following function as shown in React-Router Docs with the only exception is that I'm using a prop I pass to the component to check whether the user is authenticated or not:
const PrivateRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render={(props) => {
return (
props.isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: '/login',
state: { from: props.location },
}}
/>
)
);
}
}
/>
);
};
Here's what's being rendered:
return (
<React.Fragment>
{ isLoading ? (<SplashScreenView />) : (
<Router>
<ContentContext.Provider value={appContent}>
<TitleBarRouter />
<Switch>
<Route exact path="/login" render={props => <LoginView {...props} performAuthentication={this.performAuthentication} isAuthenticating={isAuthenticating} isAuthenticated={isAuthenticated} />} />
<PrivateRoute exact path="/" render={props => <MainView {...props} checked={appContent.checked} elapsed={elapsed} />} />
<PrivateRoute exact path="/settings" component={SettingsView} />
<PrivateRoute exact path="/card" component={CardView} />
<PrivateRoute exact path="/memberships" component={ActiveMembershipsView} />
</Switch>
<TabBarRouter />
</ContentContext.Provider>
</Router>
)}
</React.Fragment>
);
And this is how I'm setting the state. This function is passed as prop to the /login route where it's called on a button click.
performAuthentication(obj) {
this.setState({
isAuthenticating: true,
}, () => {
setTimeout(() => {
this.setState({
isAuthenticating: false,
isAuthenticated: true,
});
}, 2000);
});
}
The error I'm getting when I set isAuthenticated to true and the PrivateRoutes are supposed to render is Invariant Violation: Maximum update depth exceeded. I managed to track it down to the PrivateRoute function which causes an infinite loop, which in turn should be causing the error. I can't for the life of me figure why this is happening though. I'd appreciate any pointers.
I have this piece of code in my app.js and have configured a PrivateRoute which requires the user to login and only allows access if the cookie is set. However, I would like to restrict users trying to hit /login after they have successfully logged in. I used the reverse logic of the PrivateRoute and created LoginRoute which serves the purpose but would like to know if there is a better approach.
import React from 'react';
import {
BrowserRouter as Router,
Route,
Switch,
Redirect
} from 'react-router-dom';
import { ConnectedRouter } from 'connected-react-router'
import cookies from 'cookies-js';
import Home from './homeComponent';
import Login from './loginComponent';
import Dashboard from './dashboardComponent';
import NoMatch from './noMatchComponent';
const App = ({ history }) => {
return (
<ConnectedRouter history={history}>
<Switch>
<Route exact={true} path="/" component={Home} />
<LoginRoute path="/login" component={Login} />
<PrivateRoute path="/dashboard" component={Dashboard} />
<Route component={NoMatch} />
</Switch>
</ConnectedRouter>
);
};
const LoginRoute = ({ component: Component, rest }) => (
<Route {...rest} render={(props) => (
cookies.get('access-token')
? <Redirect to={{
pathname: '/dashboard',
state: { from: props.location }
}} />
: <Component {...props} />
)} />
)
const PrivateRoute = ({ component: Component, rest }) => (
<Route {...rest} render={(props) => (
cookies.get('access-token')
? <Component {...props} />
: <Redirect to={{
pathname: '/login',
state: { from: props.location }
}} />
)} />
)
export default App;
There are a few ways to handle Private Routes, one such way is to write a custom Login route as you have written which prevents user from visiting /login if he/she is already loggedIn. The only correction that you need to make in your route is to use rest syntax correctly
const LoginRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
cookies.get('access-token')
? <Redirect to={{
pathname: '/dashboard',
state: { from: props.location }
}} />
: <Component {...props} />
)} />
)
and PrivateRoute
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
cookies.get('access-token')
? <Component {...props} />
: <Redirect to={{
pathname: '/login',
state: { from: props.location }
}} />
)} />
)
The other way to handle this would be an authentication HOC.
const RequireAuth = (Component) => {
return class App extends Component {
render() {
const { location } = this.props;
if (cookies.get('access-token')) {
if(location.pathname === '/login') {
return <Redirect to={'/dashboard'} />
}
return <Component {...this.props} />
}
return <Redirect to="/login"/>
}
}
}
export { RequireAuth }
and you would use it like
const App = ({ history }) => {
return (
<ConnectedRouter history={history}>
<Switch>
<Route exact={true} path="/" component={Home} />
<Route path="/login" component={RequireAuth(Login)} />
<Route path="/dashboard" component={RequireAuth(Dashboard)} />
<Route component={NoMatch} />
</Switch>
</ConnectedRouter>
);
};