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;
Related
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>
);
I am a beginner with ReactJS and i am in trouble with the optionnal parameters in url.
I use react-router-dom 5.1.2.
When i try to create a Route with a component which have a optionnal parameters, this route will keep the parameters name if there is no values for params. If i create a Link with parameter all works fine.
For example in my sideBar the path to access to AudioComponent is (the link is generated with the route component):
<a class="sidebar-link" href="/callcenter/soundplayer/:agentName?/:date?">Audios</a>
My componentRoute :
{
path: "/callcenter/soundplayer/:agentName?/:date?",
name: "Audios",
component: SoundFileRow,
isShow: true,
access: "staffAccess"
},
And my Routes.js (I download a template so the Route code was already like that, except for conditions)
const childRoutes = (Layout, routes, navBarAccess) => routes.map(({ children, path, realPath, component: Component, access }, index) =>
children ? (
// Route item with children
children.map(({ path, component: Component , access}, index) => (
<Route
key={index}
path={path}
exact
render={props => (
(checkIsAuth())?
checkHasAccess(access) ?
<Layout>
<Component {...props} />
</Layout> :
<AuthLayout>
<Page404 />
</AuthLayout> :
<Redirect to={{ pathname: '/auth/sign-in' }} />
)}
/>
))
) : (
// Route item without children
<Route
key={index}
path={path}
exact
render={props => (
(checkIsAuth()) ?
(checkHasAccess(access)) ?
<Layout>
<Component {...props} />
</Layout> :
<AuthLayout>
<Page404 />
</AuthLayout> :
(path !== "/auth/sign-in") ?
<Redirect to={{ pathname: '/auth/sign-in' }} /> :
<Layout>
<Component {...props} />
</Layout>
)}
/>
) );
and my render :
render() {
return ( <Router>
<ScrollToTop>
<Switch>
{childRoutes(DashboardLayout, dashboardRoutes)}
{childRoutes(DashboardLayout, defaultRoutes)}
{childRoutes(AuthLayout, signInRoutes)}
<Route
render={() => (
(checkIsAuth()) ?
<AuthLayout>
<Page404 />
</AuthLayout> :
<Redirect to={{ pathname: '/auth/sign-in' }} />
)}
/>
</Switch>
</ScrollToTop> </Router> ); }
Someone already had this problem ?
Thanks in advance for help :)
I solved it. The problem was from the template which used the same path to know the url params and to dislay url in href. I just create a new parameter in my component :
{
path: "/callcenter/soundplayer",
realPath: "/callcenter/soundplayer/:agentName?/:date?",
name: "Audios",
component: SoundFileRow,
isShow: true,
access: "staffAccess"
},
Then I add it in my router.js :
const childRoutes = (Layout, routes, navBarAccess) =>
routes.map(({ children, path, realPath, component: Component, access }, index) =>
children ? (
// Route item with children
children.map(({ path, realPath, component: Component , access}, index) => (
<Route
key={index}
path={realPath}
exact
render={props => (
(checkIsAuth())?
checkHasAccess(access) ?
<Layout>
<Component {...props} />
</Layout> :
<AuthLayout>
<Page404 />
</AuthLayout> :
<Redirect to={{ pathname: '/auth/sign-in' }} />
)}
/>
))
) : (
// Route item without children
<Route
key={index}
path={realPath}
exact
render={props => (
(checkIsAuth()) ?
(checkHasAccess(access)) ?
<Layout>
<Component {...props} />
</Layout> :
<AuthLayout>
<Page404 />
</AuthLayout> :
(path !== "/auth/sign-in") ?
<Redirect to={{ pathname: '/auth/sign-in' }} /> :
<Layout>
<Component {...props} />
</Layout>
)}
/>
)
);
Hope it will be usefull for someone on day !
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
I get this error:
Element 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
When clicking this link:
<li><NavLink activeClassName="active" to={{pathname: '/add-new'}}>Add new Recipe</NavLink>{' '} </li>
My route looks like this:
<PrivateRoute
exact
path='/add-new'
render={(props) =>
<NewRecipe
{...props}
addRecipe={this.addRecipe}/>
}
authenticated={authenticated}
/>
PrivateRoute.js looks like this:
import React from "react";
import { Route, Redirect } from "react-router-dom";
export default function PrivateRoute({
component: Component,
authenticated,
...rest
}) {
console.log(...rest);
return (
<Route
{...rest}
render={props =>
authenticated === true ? (
<Component {...props} {...rest} />
) : (
<Redirect to="/login" />
)
}
/>
);
}
Error occurs since I switched from Component={NewRecipe} to Render={...} since I need to pass a function.
PrivateRoute skips render props (instead of call it), fix might be something like this (notice render() logic):
export default function PrivateRoute({
component: Component,
authenticated,
render,
...rest
}) {
return (
<Route
{...rest}
render={props =>
authenticated === true ? (
render ? render(props) : <Component {...props} {...rest} />
) : (
<Redirect to="/login" />
)
}
/>
);
It's overcomplex a bit, but I hope can help you understand the idea on how render prop might be caught.
Other solution is to change /add-new to be passed as a component:
<PrivateRoute
exact
path='/add-new'
addRecipe={this.addRecipe}
component={NewRecipe}
authenticated={authenticated}
/>
I try to use typescript to rewrite the example of 'Redirects (Auth)', but meet one problem that the function cannot receive the props as I expect.
Here is my code:
router setting and function of private router:
export default () => (
<Router>
<Switch>
<PrivateRoute exact path="/welcome" component={Welcome}/>
<Route component={NoMatch}/>
</Switch>
</Router>)
const PrivateRoute = (component: any, ...rest: Array<any>) => {
console.log(component, rest)
return (
<Route {...rest} render={props => (
isAuthenticated() ? (
<div>
<Header/>
<Sider/>
<div className="content slide-in">
<component {...props}/>
</div>
</div>
) : (
<Redirect to={{
pathname: '/',
state: { from: props.location }
}}/>
)
)}/>
)}
I expect that the param of component is the Component of 'welcome', and the param of rest are other params such as 'exact' and 'path', but actually get the params as in above image.
component:
rest:
Anyone can help me to solve this problem?
Many thanks!