React HOC can't pass props to enhanced component - javascript

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

Related

Create React element from object attribute (with props)

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()}

Use a global state with Context

I'm currently trying to set a globalstate into my app. It's just about counting points, so a global "score".
Context seemed easy and I followed some tutorials, but it keeps sending errors.
In my app, I am using React and BrowserRouter to switch between pages.
App.jsx You can see, I tried to wrap with the Store Component
export default function App() {
return (
<main>
<Routes>
<Store>
<Route path="/" element={<Welcome />} />
<Route path="/whoareyou" element={<WhoAreYou />} />
<Route path="/questionone" element={<QuestionOne />} />
<Route path="/questiontwo" element={<QuestionTwo />} />
<Route path="/questionthree" element={<QuestionThree />} />
<Route path="/result" element={<Result />} />
</Store>
</Routes>
</main>
);
}
Points.js This is my Context file
import React, { useState, createContext } from "react";
const initialState = { points: 0 };
export const Context = createContext(initialState);
export const Store = ({ children }) => {
const [state, setState] = useState(initialState);
return (
<Context.Provider value={[state, setState]}>{children}</Context.Provider>
);
};
Component Here is my component, where I want to count a 1 into the "points" Score
export default function WhoAreYou() {
const [state, setState] = useContext(Context);
return (
<StyledSection variant="center">
<StyledNavLink to="/questionone">
<StyledButton
type="button"
variant="next-button"
onClick={() => {
console.log(state.points);
}}
>
NEXT
</StyledButton>
</StyledNavLink>
</StyledSection>
);
}
Errors
This are the error messages:
undefined is not an object (evaluating 'element.type.name')
Warning: 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.
Check the render method of App.
App
Router
BrowserRouter
To See the whole code, you can see this CodeSandBox:
https://codesandbox.io/s/empty-https-gpkq0l?file=/src/components/pages/WhoAreYou.jsx:581-1422
I would be very thankful if you guys could help!!
Import Store as named import (you exported it as named export)
import { Store } from "../src/components/context/Points";

How do I turn a JSX Element into a Function Component in React?

My React app has the following in App.js:
const App = () => (
<Router>
<Switch>
... various routes, all working fine ...
<Route exact path={ROUTES.DASHBOARD} render={(props) => <Dashboard {...props} />}/>
</Switch>
</Router>
);
I'm getting an error on Dashboard, which says JSX element type 'Dashboard' does not have any construct or call signatures.
This is because Dashboard is created like this:
const DashboardPage = ({firebase}:DashboardProps) => {
return (
<div className="mainRoot dashboard">...contents of dashboard...</div>
);
}
const Dashboard = withFirebase(DashboardPage);
export default Dashboard;
and withFirebase is:
import FirebaseContext from './firebaseContext';
const withFirebase = (Component:any) => (props:any) => (
<FirebaseContext.Consumer>
{firebase => <Component {...props} firebase={firebase} />}
</FirebaseContext.Consumer>
);
export default withFirebase;
So withFirebase is exporting a JSX element, so that's what Dashboard is. How can I ensure that withFirebase is exporting a Component instead?
So withFirebase is exporting a JSX element, so that's what Dashboard is. How can I ensure that withFirebase is exporting a Component instead?
withFirebase is not creating a JSX element, it is creating a function which creates a JSX Element -- in other words that's a function component. Perhaps it helps to type it properly.
const withFirebase = <Props extends {}>(
Component: React.ComponentType<Omit<Props, "firebase"> & { firebase: Firebase | null }>
): React.FC<Props> => (props) => (
<FirebaseContext.Consumer>
{(firebase) => <Component {...props} firebase={firebase} />}
</FirebaseContext.Consumer>
);
Those type are explained in detail in this answer. Is your context value sometimes null? Can your DashboardPage handle that, or do we need to handle it here? Here's one way to make sure that DashboardPage can only be called with a valid Firebase prop.
const withFirebase = <Props extends {}>(
Component: React.ComponentType<Omit<Props, "firebase"> & { firebase: Firebase }>
): React.FC<Props> => (props) => (
<FirebaseContext.Consumer>
{(firebase) =>
firebase ? (
<Component {...props} firebase={firebase} />
) : (
<div>Error Loading Firebase App</div>
)
}
</FirebaseContext.Consumer>
);
Now that we have fixed the HOC, your Dashboard component has type React.FC<{}>. It's a function component that does not take any props.
You do not need to create an inline render method for your Route (this will actually give errors about incompatible props). You can set it as the component property component={Dashboard}.
complete code:
import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
// placeholder
class Firebase {
app: string;
constructor() {
this.app = "I'm an app";
}
}
const FirebaseContext = React.createContext<Firebase | null>(null);
const withFirebase = <Props extends {}>(
Component: React.ComponentType<Omit<Props, "firebase"> & { firebase: Firebase }>
): React.FC<Props> => (props) => (
<FirebaseContext.Consumer>
{(firebase) =>
firebase ? (
<Component {...props} firebase={firebase} />
) : (
<div>Error Loading Firebase App</div>
)
}
</FirebaseContext.Consumer>
);
interface DashboardProps {
firebase: Firebase;
}
const DashboardPage = ({ firebase }: DashboardProps) => {
console.log(firebase);
return <div className="mainRoot dashboard">...contents of dashboard...</div>;
};
const Dashboard = withFirebase(DashboardPage);
const App = () => {
const firebase = new Firebase();
return (
<FirebaseContext.Provider value={firebase}>
<Router>
<Switch>
<Route component={Dashboard} />
</Switch>
</Router>
</FirebaseContext.Provider>
);
};
export default App;

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

Passing props from container component to nested route component

How can i share state between AppContainer and Home component?
For example, i want put results object in the state of AppContainer for pass it to all other components.
In this app i'm not using Redux.
I've tried to use React.cloneElement in AppContainer but generate an 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.
index.js
<Router history={browserHistory}>
<AppContainer>
<Route path="/" component={Home} />
<Route path="search" component={Search} />
</AppContainer>
</Router>
AppContainer.js
render() {
return (
<div className="containerApp">
{this.props.children}
</div>
);
}
React.cloneElement is the right way to go. For example:
render() {
const childProps = {
prop1: this.state.prop1
}
return (
<div className="containerApp">
{React.cloneElement(this.props.children, childProps)}
</div>
);
}
If this gives you the error you were previously receiving then the only reason I can think is that AppContainer is being rendered without any children prior to any route match. You could mitigate this either by conditionally rendering in AppContainer:
render() {
const childProps = {
prop1: this.state.prop1
}
const { children } = this.props;
return (
<div className="containerApp">
{children && React.cloneElement(children, childProps)}
</div>
);
}

Categories

Resources