Problem using Render instead of Component in React - reactjs

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}
/>

Related

React-Router-Dom 6 - How to dynamically render a component?

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>
);

Avoid error "Element type is invalid" with React.createElement in HOC React

I want to implement the PrivateRoute HOC which will check if the user is logged in then render the corresponding component, otherwise redirect to the login page.
Here is the PrivateRoute HOC:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
const PrivateRoute = ({ component, ...rest }) => (
<Route {...rest} render={(props) => (
sessionStorage.getItem('userToken') ? (
React.createElement(component, props)
) : (
<Redirect to={{
pathname: '/login',
}} />
)
)} />
);
export default PrivateRoute;
Here's how I'm using it:
import PrivateRoute from './components/HOC/PrivateRoute';
...
render() {
if (this.props.hasError) return <h1>Sorry, something went wrong...</h1>;
if (this.props.isLoading) return <h1>Loading...</h1>;
return (
<div className="App">
<Switch>
<PrivateRoute exact path="/" component={<Home /> } />
<Route exact path="/login" render={props => <Login {...props} />} />
<Route exact path="/registration" render={props => <Registration {...props} />} />
<PrivateRoute exact path="/account" component={<Account />} />
</Switch>
</div>
);
}
But it throws an error:
I find a GitHub thread where I find a possible answer for why it happens like that, but I don't know how to implement that answer for my case:
I tried some experimental ways to achieve that but everything fails.
React.createElement accepts a type or a React component before it invoked.
React.createElement(
type,
[props],
[...children]
)
The type argument can be either a tag name string (such as 'div' or 'span'), a React component type (a class or a function), or a React fragment type.
const Component = () => <>Hello</>;
// Good
React.createElement(Component)
React.createElement("div")
// Bad
React.createElement(<div/>)
In your case you use the "bad" version.
You should use cloneElement or fix it with a wrapper:
// Good
React.cloneElement(<Component />)
// Or make a wrapper
React.createElement(() => <Component />));
So finally:
// Change to clone
React.cloneElement(component, props)
// **OR** make a wrapper
<PrivateRoute exact path="/" component={() => <Home />} />

Error when using Render method but not component in Route [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 3 years ago.
Improve this question
I am currently running into the 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."
I have searched the error up online and see that most of the time it is caused by someone incorrectly exporting or importing a component but I cannot seem to find where I have done that wrong.
Still, I have managed to find a way to render my component. When I use
PrivateRoute path="/protected" component={Protected} it works, but if I use PrivateRoute with render (render={() => < Protected/>}) it seems to give me the error. I am assuming it has to do with either the PrivateRoute or how im calling the component. Im going to include my dependencies to make sure that I am not calling it wrong. Also here is a link to my codesandbox: https://codesandbox.io/s/dawn-cherry-3yv8e?fontsize=14.
import React from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Route,
Link,
Switch,
Redirect,
withRouter
} from "react-router-dom";
import "./styles.css";
import PrivateRoute from "./PrivateRoute";
import Login from "./Login";
function Protected() {
return <h3>Protected</h3>;
}
function Public() {
return <h3>public</h3>;
}
class App extends React.Component {
render() {
return (
<React.Fragment>
<Router>
<div>
<ul>
<li>
<Link to="/public">Public Page</Link>
</li>
<li>
<Link to="/protected">Protected Page</Link>
</li>
</ul>
<Switch>
<Route path="/public" component={Public} />
<Route path="/login" render={() => <Login />} exact />
<PrivateRoute
path="/protected"
// component={Protected}
render={() => <Protected />}
exact
/>
</Switch>
</div>
</Router>
</React.Fragment>
);
}
}
import React from "react";
import { Route, Redirect } from "react-router-dom";
import fakeAuth from "./fakeAuth";
function PrivateRoute({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={props =>
fakeAuth.isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
}
export default PrivateRoute;
If you would like to see the rest of the code check the codesandbox that is linked above. Thanks for any help
You should name your PrivateRoute prop accordingly, quick fix:
<PrivateRoute
path="/protected"
toRender={Protected}
// toRender={() => <Protected />} // This should also work
exact
/>
// Here we use render prop as Component
function PrivateRoute({ toRender: Component, ...rest }) {
return (
<Route
{...rest}
render={props => (
fakeAuth.isAuthenticated
? (
<Component {...props} />
)
: (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
)}
/>
);
}
Working example

React Router match property null when using custom Route component

I am using the ConnectedRouter component and a custom Route like so:
const PrivateRoute = ({ layout: Layout, component: Component, rest }) => (
<Route
{...rest}
render={matchProps => (
<AuthConsumer>
{({ authenticated }) =>
authenticated ? (
<Layout pathname={matchProps.location.pathname}>
<Component {...matchProps} />
</Layout>
) : (
<Redirect to={{ pathname: '/login', state: { from: matchProps.location } }} />
)
}
</AuthConsumer>
)}
/>
)
which I use like this:
<Switch>
<PrivateRoute
exact
path={`${match.url}someroute/:id`}
layout={Layout}
component={SomePage}
/>
</Switch>
And a component like so:
import React from 'react'
const SomePage = ({ match }) => {
console.log('MATCH ', match.params)
return <div>TESTING</div>
}
export default SomePage
In this case, match is empty, and it thinks its on the '/' route (even though location prop says its on /someroute/123.
On the other hand, this works:
<Route
exact
path={`${match.url}someroute/:id`}
component={SomePage}
/>
and match gets updated properly.
I'm stumped as to why this is happening. Any help would be appreciated!
Ah, I figure it out. rest should have been ...rest, the <Route> component wasn't getting the props passed down correctly!

Using render in react router dom with a private route

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;

Resources