My old method:
<Route
key={i}
path={path}
render={(props) => {
if (!localStorage.getItem("token")) {
<Redirect
to={{ pathname: "/login", state: { from: props.location } }}
/>
}
return (
<AuthLayout>
<Component {...props} />
</AuthLayout>
);
}}
/>
Replacing render with the new element gives me:
Functions are not valid as a React child. This may happen if you return a Component instead of from render
Apparently the new API simply expects:
<Route
key={i}
path={path}
element={
<Component />
}
/>
What I'm really trying to accomplish is to dynamically render the component as such:
{authProtectedRoutes.map(({ path, Component }, i) => {
<Route
key={i}
path={path}
element={
// If no auth token, redirect to login
if (!token) {
<Navigate to="/login" />
} else {
<AuthLayout>
<Component />
</AuthLayout>
}
}
/>
})}
Not sure how to do this ...
EDIT:
My array of components is as such:
const authProtectedRoutes = [
{ path: "/dashboard", Component: Dashboard },
{ path: "/pages-starter", Component: StarterPage },
When I try to return Component in my loop I get:
React.jsx: type is invalid -- expected a string (for built-in
components) or a class/function (for composite components) but got:
undefined. You likely forgot to export your component from the file
it's defined in, or you might have mixed up default and named imports.
element={
// If no auth token, redirect to login
if (!token) {
<Navigate to="/login" />
} else {
<AuthLayout>
<Component />
</AuthLayout>
}
}
You can't do an if in the middle of jsx, but you can do a conditional operator:
element={!token ? (
<Navigate to="/login" />
) : (
<AuthLayout>
<Component />
</AuthLayout>
)}
The element prop expects a ReactNode (a.k.a. JSX) and not javascript (i.e. the if-statement).
Since it seems you render your authenticated routes in bulk a more optimal solution would be to wrap them all in a single AuthLayout component that checks the token. Instead of rendering the children prop it renders an Outlet for nested routes to be rendered into.
Example:
const AuthLayout = ({ token }) => {
// ... existing AuthLayout logic
return token
? (
<div /* awesome auth layout CSS style */>
...
<Outlet /> // <-- nested routes render here
</div>
)
: <Navigate to="/login" />;
};
Don't forget to return the Route from the map callback.
<Route element={<AuthLayout token={token} />}>
{authProtectedRoutes.map(({ path, Component }) => (
<Route key={path} path={path} element={<Component />} />
))}
</Route>
Nice routing-related question. First of all, I found useful code example from react-router-dom's github: https://github.com/remix-run/react-router/blob/2cd8266765925f8e4651d7caf42ebe60ec8e163a/examples/auth/src/App.tsx#L104
Here, instead of putting some logics inside "element" or "render" authors suggest to implement additional RequireAuth component and use it in routing setup like following:
<Route
path="/protected"
element={
<RequireAuth>
<SomePageComponent />
</RequireAuth>
}
....
This approach would allow to incapsulate auth-related checks inside this new RequireAuth component and as a result make your application routing setup more "lightweight"
As a "brief" example, I created following piece of code you could reference to:
function App() {
return (
<BrowserRouter>
<AppRoutes />
</BrowserRouter>
);
}
const RequireAuth = ({ children }) => {
const token = localStorage.getItem('token');
const currentUrl = useHref();
return !token ? (
<Navigate to={`/login?redirect=${currentUrl}`} />
) : (
children
);
};
const authProtectedRoutes = [
{ path: '/', component: PaymentsPage },
{ path: '/user', component: UserInfoPage },
];
const AppRoutes = () => (
<Routes>
{authProtectedRoutes.map((r) => (
<Route
key={r.path}
path={r.path}
element={
<RequireAuth>
<AuthLayout>
<r.component />
</AuthLayout>
</RequireAuth>
}
/>
))}
<Route path="/login" element={<LoginPage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
);
Related
I just encountered a problem with the react-router element:
import Home from '../../views/newssandbox/home/Home'
import RightList from '../../views/newssandbox/right-manage/RightList'
import RoleList from '../../views/newssandbox/right-manage/RoleList'
.....
const LocalRouterMap = {
"/home": Home,
"/user-manage/list": UserList,
"/right-manage/role/list": RoleList,
"/right-manage/right/list": RightList,
...
}
I want my code to work as this
<Route path='/home' element={<Home />} />
<Route path='/user-manage/list' element={<UserList />} />
<Route path='/right-manage/role/list' element={<RoleList />} />
How should I change this one?
backRouterList.map(item => {
{/* console.log(item.key) */}
<Route path={item.key} key={item.key} element={LocalRouterMap[item.key]} />
})
Given an array of route path-component pairs:
const LocalRouterMap = {
"/home": Home,
"/user-manage/list": UserList,
"/right-manage/role/list": RoleList,
"/right-manage/right/list": RightList,
...
}
When mapping the array you will need to first create a valid local React component variable and render it out on the Route component's element prop as JSX.
Example:
backRouterList.map(item => {
const Component = LocalRouterMap[item.key];
return (
<Route path={item.key} key={item.key} element={<Component />} />
);
})
You could optimize this a bit by moving the JSX part to the LocalRouterMap object.
const LocalRouterMap = {
"/home": <Home />,
"/user-manage/list": <UserList />,
"/right-manage/role/list": <RoleList />,
"/right-manage/right/list": <RightList />,
...
}
Then you can just pass the element.
backRouterList.map(item => (
<Route
path={item.key}
key={item.key}
element={LocalRouterMap[item.key]}
/>
))
I'm using module federation from webpack and my core app contained all routes to the rest of the app. What works fine is that inside the Switch, I just had each AuthRoute or Route manually rather than using the map. Suspense was wrapping the Switch so that the direct children are just Route. I'm now doing some splitting but I can't get it to work. Any ideas?
my routes are set up as so (and localRoutes is at the bottom):
const routes = [
...localRoutes,
// ...remoteRoutes
];
Inside my BrowserRouter I map routes based on whether the user is authorised for that route or not. I suspect the problem is here but don't understand why Route or AuthRoute that returns a Route won't work since it's a directly under the Switch.
<Switch>
{routes.map((route) => {
console.log(route)
route.auth ?
<AuthRoute
key={route.path}
path={route.path}
component={route.component}
exact={route.exact}
requiredRoles={route.requiredRoles}
/>
:
<Route
key={route.path}
path={route.path}
component={route.component}
exact={route.exact}
/>
})}
<Redirect to='/login' />
</Switch>
where authRoute:
const AuthRoute = ({ Component, path, exact, requiredRoles }) => {
const isLoggedIn = true // or false
const roles = ['admin', 'sth_else']
const userHasRequiredRole = intersection(requiredRoles, roles).length > 0
const message = userHasRequiredRole ? 'Please log in to view this page' : "You can't be here!"
return (
<Route
exact={exact}
path={path}
render={(props) =>
isLoggedIn && userHasRequiredRole
? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: userHasRequiredRole ?
'/login' :
'/modules',
state: {
message,
requestedPath: path
}
}}
/>
)
}
/>
);
};
export default AuthRoute;
and example routes:
const AboutPage = lazy(() => import('core/AboutPage'))
const LoginPage = lazy(() => import('core/LoginPage'))
const MyModules = lazy(() => import('core/MyModules'))
const routes = [
{
auth: true,
path: "/modules",
component: MyModules,
exact: false,
requiredRoles: [
String(UserRoles.Administrator),
String(UserRoles.AnotherRole),
String(UserRoles.Another)
]
},
{
auth: false,
path: "/about",
component: AboutPage,
exact: false,
}
];
If you are doing lazyLoad specify component as a function inside <Route>.
In your case try the following:
<Route
key={route.path}
path={route.path}
component={(props) => (<route.component {...props} />)}
exact={route.exact}
/>
I think the above code should work if you add a return before ternary operator
retur route.auth ?...
I am having similar structure in my app
<Suspense fallback={<Spinner />}>
<Switch>
<Refresh path="refresh" />
{routes.map((route) => {
return <PrivateRoute key={route.path} {...route} user={user} />;
})}
{routes.length && user && <Route component={NotFound} />}
</Switch>
</Suspense>
will try to be brief.
Dashboard component is rendering, but while hitting localhost:3000/dashboard/shipments nothing is rendering.
Not versed in the react, not sure if render={({ location })(Line 1) is causing problem.
Tried placing components/simply h4 tag in Route (Line2) but nothing working.
Necessary imports are done.
App.js
const pages = [
{
pageLink: '/dashboard',
view: Dashboard,
displayName: 'Dashboard',
showInNavbar: true,
exact: false
},....more routes.
return(
<Router>
<Route render={({ location }) => (//---------->Line 1
<React.Fragment>
<Navbar />
<Switch location={location}>
{pages.map((page, index) => {
return (
<Route
exact={page.exact}
path={page.pageLink}
component={page.view}
key={index}
/>
)
})}
<Redirect to='/' />
</Switch>
</React.Fragment>
)}
/>
</Router>
)
dashboard.js
export default function Dashboard() {
const authedUser = useSelector(state => state.authedUser);
let { path } = useRouteMatch();
if (!authedUser.loggedIn) return <Redirect to='/login' />
return (
<React.Fragment>
<section id='dashboard-component'>
<Sidebar />
<Switch>
<Route exact path={path}>
<h2>Dashboard</h2>
</Route>
<Route exact path={`/${path}/shipments`}><h4>sdsd</h4></Route>//------>Line 2
</Switch>
</section>
</React.Fragment>
)
}
You have a extra / at the start of your nested Route
<Route exact path={`/${path}/shipments`}><h4>sdsd</h4></Route>
Now path already return /dashboard. Writing path={`/${path}/shipments`} would make the route path as path={'//dashboard/shipments'}
You need to specify your child route like
<Route exact path={`${path}/shipments`}><h4>sdsd</h4></Route>
Working demo
In my app, I'd like to match all routs that end with #something.
/map#login
/info#login
and
/map#register
/map/one#register
/info#register
/info/two#register
So I can show component as popup on top of the content. How this can be done?
I found a solution for this case. It was inspired from this question in stackOverflow. Using HashRoute wrapper for Route and showing component based on location.hash.
const HashRoute = ({ component: Component, hash, ...routeProps }) => (
<Route
{...routeProps}
component={({ location, ...props }) =>
location.hash === hash && <Component {...props} />
}
/>
);
export default class App extends Component {
constructor() {
super();
}
render() {
return (
<div className='App'>
<Router history={history}>
<HashRoute hash='#login'component={Login} />
<HashRoute hash='#register' component={Register} />
<Switch>
<Route exact path='/map' component={Map} />
<Route exact path='/info' component={Info} />
</Switch>
</Router>
</div>
);
}
}
Updating/improving from the other answer here. It would better to not use the component prop as it won't create new instance of the routed component each time the Route is rendered for any reason. The custom HashRoute component should return valid JSX, either a Route component or null.
Example:
const HashRoute = ({ hash, ...routeProps }) => {
const location = useLocation();
return location.hash === hash
? <Route {...routeProps} />
: null
};
...
<Router>
<HashRoute hash='#login' component={Login} />
<HashRoute
hash='#register'
render={props => <Register {...props} otherProp />}
/>
<HashRoute hash='#something'>
<Register otherProp />
</HashRoute>
<Switch>
<Route path='/map' component={Map} />
<Route path='/info' component={Info} />
</Switch>
</Router>
Im trying to render two components within a private route using react router dom v4. This is possible using a normal Route but it does not seem to be the case when using a custom Route. I get the following error:
"Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
"
Custom route (Authenticated)
return (
<Route
{...rest}
render={props =>
this.currentUser()
? <Component currentUser={this.currentUser} {...props} />
: <Redirect
to={{
pathname: '/auth/login',
state: { from: props.location }
}}
/>
}
/>
)
Then in my routes i want something like this
return (
<div>
<Switch location={isModal ? this.prevLocation : location}>
<Authenticated path="/" exact component={Main} />
<Route path="/auth/register" exact component={Register} />
<Route path="/auth/login" exact component={Login} />
<Authenticated
path="/clients/:id/edit"
render={(props) => ( // Not working as expected. Works fine using Route instead of Authenticated
<div>
<Main />
<ClientEdit />
</div>
)}
/>
</Switch>
{isModal ?
<Authenticated
path='/clients/new'
component={ClientNew}
/>
: null}
{isModal ?
<Authenticated
path='/clients/:id/edit'
component={ClientEdit}
/>
: null}
</div>
);
I'm a little late, but for anyone still needing this, I found that this works for me.
export function PrivateRoute({ component: Component = null, render: Render = null, ...rest }) {
const authService = new AuthService();
return (
<Route
{...rest}
render={props =>
authService.isAuthenticated ? (
Render ? (
Render(props)
) : Component ? (
<Component {...props} />
) : null
) : (
<Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)
}
/>
);
}
And in my routes I use it like so:
<PrivateRoute
path="/some-route/edit"
render={props => <MyComponent {...props} isAwesome={true} />} />
In your protectedRoute component, you are not receiving or utilizing render prop which you are sending in the line:
render={(props) => (
<div>
<Main />
<ClientEdit />
</div>
)}
instead of using render send the component in component prop like :
component={(props) => (
<div>
<Main />
<ClientEdit />
</div>
)}
Also check react router's docs to see when to use component prop and when to use render prop. It would be much better if you can change your protectedRoute to handle both.
I think you need to create a custom component returning :
return(
<div>
<Main />
<ClientEdit />
</div>)
Then import it and use it in your authenticated component like this :
<Authenticated
path="/clients/:id/edit"
component={CustomComponent}
/>
Edit: you can also handle render prop in your Authenticated component if provided :
if (this.props.render && this.currentUser()) {
return(
<Route
{...rest}
render={this.props.render}
/>
} else {
return (
<Route
{...rest}
render={props => this.currentUser() ?
<Component currentUser={this.currentUser} {...props} /> :
<Redirect
to={{
pathname: '/auth/login',
state: { from: props.location }
}}
/>
}
/>
)
}
import React from 'react';
import { Route, Redirect } from "react-router-dom";
const PeivateRoute = ({ component: component, ...rest }) => {
return (
<Route
{...rest}
render = {props => (false ? <component {...props}/> : <Redirect to="/" />)}
/>
);
};
export default PeivateRoute;