Create React element from object attribute (with props) - javascript

In the following code I would like to pass props to the e.component element
But i'm getting an error :
Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
How can i do that ?
element={<MainComponent msg={msg} />} works but it does not meet my needs ❌❌
The element must be called like this e.component ✔️✔️
const routes = [
{
name: `main`,
path: `/main`,
component: <MainComponent />,
},
]
function MainComponent(props) {
return <h2>{`Hello ${props.msg}`}</h2>
}
function App() {
const msg = `world`
return (
<BrowserRouter basename="/">
<Routes>
{routes.map((e, j) => {
return (
<Route
key={`${j}`}
path={e.path}
// want to pass "msg" props to e.component ???????
element={<e.component msg={msg} />}
/>
)
})}
</Routes>
</BrowserRouter>
)
}

If you want to be able to pass additional props at runtime then you can't pre-specify MainComponent as JSX. Instead you could specify MainComponent as a reference to the component instead of JSX, and then render it as JSX when mapping. Remember that valid React components are Capitalized or PascalCased.
Example:
const routes = [
{
name: 'main',
path: '/main',
component: MainComponent,
},
];
function MainComponent(props) {
return <h2>Hello {props.msg}</h2>;
}
function App() {
const msg = 'world';
return (
<BrowserRouter basename="/">
<Routes>
{routes.map((e, j) => {
const Component = e.component;
return (
<Route
key={j}
path={e.path}
element={<Component msg={msg} />}
/>
)
})}
</Routes>
</BrowserRouter>
);
}

Try this
const routes = [
{
name: `main`,
path: `/main`,
component: (props) => <MainComponent {...props} />,
},
]```

If you want to render a component instance, you have to:
pass: component: <MyComponent />
use: {component}
If you have to pass a component:
pass: Component: MyComponent
use: <Component />
If you want to pass a render prop:
pass: component: (params) => <MyComponent {...params} />
use: {component()}

Related

Can't print child components of nested routes on React Router v5

I can't seem to figure out how to print child routes in React Router v5. Here is how I have setup my application.
1) index.jsx
ReactDOM.render(
<Provider store={store}>
<IntlProvider defaultLocale="en" locale="en" messages={messages}>
<ThemeProvider theme={theme}>
{Routes()}
</ThemeProvider>
</IntlProvider>
</Provider>,
root,
);
2) Routes.jsx
export default function Routes() {
return (
<ConnectedRouter history={history}>
<Switch>
<Route path="/welcome" component={App} />
<Route component={UnknownPage} />
</Switch>
</ConnectedRouter>
);
}
3) App.jsx
const App = ({ location }) => (
<div>
<DialogMount />
<RefreshSession />
<Masthead />
<Navigation />
<PageWrapper>
<NavTabs location={location} />
<ContentWrapper>
<Alert />
<Switch>
{generateRoutes(routesConfig)}
</Switch>
</ContentWrapper>
</PageWrapper>
</div>
);
4) generateRoutes method
export const generateRoutes = (routes = []) => Object.values(routes).map((route) => {
if (route.redirect) {
return [];
} else if (route.children) {
return (
<Route key={route.path} path={route.path}>
<Switch>
{generateRoutes(route.children)}
</Switch>
</Route>
);
}
return <Route key={route.path} path={route.path} component={route.component} />;
}).reduce((navigation, route) => navigation.concat(route), []);
5) routesConfig
const routesConfig = {
parent: {
path: 'parent',
name: 'parent',
children: {
child1: {
path: 'child1',
name: 'child1',
component: Child1,
},
},
},
};
The problem is, from my App.jsx, everything until the NavTabs is being rendered. Just the routed part of it is not being rendered. I know I am missing something very silly here but can't seem to figure out.
Any help is appreciated.
Edit after Shubham's answer:
I made the changes, but still facing the same issue. However instead of
render={props => <route.component {...props} />}
I used
children={props => <route.component {...props} />}.
This seems to be loading the components, but now I see errors as such:
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.
Check the render method of `Licensing`.
at createFiberFromTypeAndProps (react-dom.development.js:23965)
at createFiberFromElement (react-dom.development.js:23988)
at createChild (react-dom.development.js:13628)
at reconcileChildrenArray (react-dom.development.js:13900)
at reconcileChildFibers (react-dom.development.js:14305)
at reconcileChildren (react-dom.development.js:16762)
at updateHostComponent (react-dom.development.js:17302)
at beginWork (react-dom.development.js:18627)
at HTMLUnknownElement.callCallback (react-dom.development.js:188)
at Object.invokeGuardedCallbackDev (react-dom.development.js:237)
The issue is happening because unless you specify the nested routes within the rendered component itself, you need to provide the entire pathname to it.
The solution is to pass on a prefix to append before the pathname. Also we need a trailing /
const generateRoutes = (routes = [], prefix = "") =>
Object.values(routes)
.map(route => {
console.log(prefix);
if (route.redirect) {
return [];
} else if (route.children) {
return (
<Route key={route.path} path={`${prefix}/${route.path}`}>
<Switch>
{generateRoutes(route.children, prefix + "/" + route.path)}
</Switch>
</Route>
);
}
return (
<Route
path={`${prefix}/${route.path}`}
key={route.path}
render={props => <route.component {...props} />}
/>
);
})
.reduce((navigation, route) => navigation.concat(route), []);
Working DEMO

Children Component undefined

See the below function i am creating the Auth routes and getting the children undefined and shows blank page. In App.js i am using the private route as you can see below and when i use simple Route instead of PrivateRoute its shows the Login component
<PrivateRoute exact path="/" name="Login" render={props => <Login {...props}/>} />
Here is is My PrivateRoute.js. When i console the children its shows undefined
function PrivateRoute({ children, ...rest }) {
const token = cookie.get('token');
return (
<Route
{...rest}
render={({ location }) =>
!token ? (
children
) : (
<Redirect
to={{
pathname: "/dashboard",
state: { from: location }
}}
/>
)
}
/>
);
}
export default Private Route;
I usually use something like it. Let me know if it works for you.
Here is my privateRoute.js component:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { isAuthenticated } from 'auth';
export default function PrivateRoute({ component: Component, ...rest }) {
return (
<Route {...rest} render={
props => (
isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/', state: { from: props.location } }} />
)
)} />
);
}
Here I have another file in the root auth.js:
export const isAuthenticated = () => {
return Boolean(localStorage.getItem('jwttoken'));
}
export const login = ({email, password}) => {
// Logic
localStorage.setItem('jwttoken', 'jkdsalkfj');
return true;
}
export const logout = () => {
localStorage.removeItem('jwttoken');
}
You can call it like:
<PrivateRoute
component={ProjectPage}
path="/project/:id"
/>
Children refer to what's inside the tag
<PrivateRoute>
<PrivateComponent/>
</PrivateRouter>
Here your children would be <PrivateComponent/>
You're not passing anything inside your PrivateRoute in your example. So you'll have undefined

How to pass props in React Components correctly?

I have routes array, which pass into RouteComponent
const info = [
{ path: '/restaurants/:id', component: <Restaurant match={{ params: '' }} /> },
{ path: '/restaurants', component: <ListRestaurant match={{ path: '/restaurants' }} /> }
];
I use Axios for connection with back-end
Restaurant Component:
async componentDidMount() {
this.getOne();
}
getOne() {
const { match } = this.props;
Api.getOne('restaurants', match.params.id)
Restaurant Component:
When I see console there is en error like this
So, what can be passed as the props? Can't find solution.
Thanks in advance
App.js
import ...
import info from './components/info/routes';
class App extends Component {
render() {
const routeLinks = info.map((e) => (
<RouteComponent
path={e.path}
component={e.component}
key={e.path}
/>
));
return (
<Router>
<Switch>
{routeLinks}
</Switch>
</Router>
);
}
}
RouteComponent.js
import { Route } from 'react-router-dom';
class RouteComponent extends Component {
render() {
const { path, component } = this.props;
return (
<Route path={path}>
{component}
</Route>
);
}
}
RouteComponent.propTypes = {
path: PropTypes.string.isRequired,
component: PropTypes.object.isRequired,
};
EDITED 22/03/2020
Line with gives this: Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
Check the render method of Context.Consumer.
class RouteComponent extends Component {
render() {
const { path, component } = this.props;
return (
<Route path={path} component={component} />
);
}
}
RouteComponent.propTypes = {
path: PropTypes.any.isRequired,
component: PropTypes.any.isRequired,
};
But as you see, I make PropTypes 'any'
Ok there are some changes you will need to do and might not be enough. So let me know if does not work in comments.
Step one
send component correctly to routes
class RouteComponent extends Component {
render() {
const { path, component } = this.props;
return (
<Route path={path} component={component}/>
);
}
}
Step two
Send JSX element, not JSX object
const info = [
{ path: '/restaurants/:id', component: Restaurant },
{ path: '/restaurants', component: ListRestaurant }
];
Import RouteProps from react-router-dom and use it as the interface for props in routecomponent.js.
Then, instead of calling component via expression, call it like a component, i.e.
Eg,
function Routecomponent({ component: Component, ...rest }: RouteProps) {
if (!Component) return null;
return (
<Route
{...rest}
<Component {...props} />
/>}
)
}

React HOC can't pass props to enhanced component

I have this piece of code:
const defaultValue = new Api()
const ApiContext = React.createContext(defaultValue);
const ApiProvider = ApiContext.Provider;
const ApiConsumer = ApiContext.Consumer;
const withApi = (Enhanced: any) => {
return (
<ApiConsumer>
{api => {
return <Enhanced api={api}/>;
}}
</ApiConsumer>
)
}
export default ApiContext;
export {ApiContext, ApiProvider, ApiConsumer, withApi};
And in my app, I have something like this:
const api = new ApiManager({...});
ReactDOM.hydrate(
<Provider store={store}>
<BrowserRouter>
<ApiProvider value={api}>
<Main />
</ApiProvider>
</BrowserRouter>
</Provider>, document.querySelector('#app')
);
But this line return <Enhanced api={api}/>; causes these errors:
1.
Warning: React.createElement: type is invalid -- expected a string
(for built-in components) or a class/function (for composite
components) but got: . Did you accidentally
export a JSX literal instead of a component?
2.
Uncaught Invariant Violation: Element type is invalid: expected a
string (for built-in components) or a class/function (for composite
components) but got: object.
3.
Uncaught (in promise) Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
Check the render method of `Context.Consumer`.
What I'm I doing wrong here?
How can I pass the api prop to an enhanced component?
[EDIT]
This is how I'm calling my component:
App.tsx
class App extends React.Component {
render() {
return (
<React.Fragment>
<Switch>
{routes.map(({ path, exact, component: C }) => {
return <Route
key={path}
path={path}
exact={exact}
render={(props) => {
return withApi(<C {...props} />);
}} />
})}
</Switch>
</React.Fragment>
)
}
}
You haven't written your withApi HOC correctly. It should return a functional component instead of JSX
const withApi = (Enhanced: any) => {
return (props) => {
return (
<ApiConsumer>
{api => {
return <Enhanced {...props} api={api}/>;
}}
</ApiConsumer>
)
}
}
and use it like
class App extends React.Component {
render() {
return (
<React.Fragment>
<Switch>
{routes.map(({ path, exact, component: C }) => {
const Comp = withApi(C);
return <Route
key={path}
path={path}
exact={exact}
render={(props) => {
return <Comp {...props}/>
}} />
})}
</Switch>
</React.Fragment>
)
}
}

React router private route not rendering

I'm trying to set up protected routes using react router v4.
I have the following, which works fine.
function PrivateRoute({ component: Component, authed, ...rest }) {
return (
<Route
{...rest}
render={props =>
authed === true ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/', state: { from: props.location } }} />
)
}
/>
);
}
However, when I change this code to:
type PrivateRouteProps = {
component: Component,
};
const PrivateRoute = ({ component, authed, ...rest }: PrivateRouteProps) => (
<Route
{...rest}
render={props =>
authed === true ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/', state: { from: props.location } }} />
)
}
/>
);
I get the error: TypeError: instance.render is not a function. When I change to (note the component: Component), everything works:
const PrivateRoute = ({ component: Component, authed, ...rest }) => (
<Route
{...rest}
render={props =>
authed === true ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/', state: { from: props.location } }} />
)
}
/>
);
The render function of App.js is as follows:
render() {
return this.state.loading === true ? (
'loading'
) : (
<BrowserRouter>
<div>
{this.renderHeader()}
<Switch>
<Route exact path="/" component={LandingPage} />
<PrivateRoute
authed={this.state.authed}
path="/home"
component={HomePage}
/>
<Route component={PageNotFoundPage} />
</Switch>
</div>
</BrowserRouter>
);
}
Why does the arrow function with PrivateRouteProps not work as expected?
In your second example you try to render the component you passed in with
<Component {...props}/>
From the way you defined your flow type I guess that you imported Component like this:
import React, {Component} from 'react'.
That means that Component does not refer to the component passed in the component prop but still to the class Component imported from react because you did not shadow it anywhere inside your functional component. Even if you imported Component in your first example it would still work because you shadowed the name Component with the value of the prop component. In the second example you did not do that.
This is why you get the error because the react class Component neither has a render() method nor does it have any other functionality you expected there.
You need to assign the prop component to another name that is capitalized, e.g. Node and then render that variable. Note that the name needs to be capitalized. Otherwise it will be interpreted as usual html node and not as a react component:
type PrivateRouteProps = {
component: Component,
};
const PrivateRoute = ({ component: Node /* assign it to a capitalized name */, authed, ...rest }: PrivateRouteProps) => (
<Route
{...rest}
render={props =>
authed === true ? (
{/* Do not use `Component` here as it refers to the class imported from react and not to your component */}
<Node {...props} />
) : (
<Redirect to={{ pathname: '/', state: { from: props.location } }} />
)
}
/>
);
You could of course also use Component as a name which will shadow the name Component from the outer scope but this is bad practice as it often leads to confusion.

Categories

Resources