I have two react components, a Layout
class and a HomePage
class:
HomePage
is a component which needs to have a products
prop.
HomePage.js
import React, { Component } from 'react';
export class HomePage extends Component {
render() {
if (!this.props.products) {
return (<div>Products not loaded yet</div>);
}
return (<div>Products loaded!</div>);
}
}
Layout
is a component that displays children coming from routes established with react-router
.
This class is in charge to pass the products
prop to children using React.cloneElement
Layout.js
import React, { Component } from 'react';
import { NavMenu } from './NavMenu';
import { Footer } from './Footer';
export class Layout extends Component {
constructor(props) {
super(props);
this.state = {
products: null,
loading: true
};
}
// Make an api call when the component is mounted in order to pass
// additional props to the children
componentDidMount() {
this.populateProductsData();
}
async populateProductsData() {
const response = await fetch('api/products/all');
const data = await response.json();
this.setState({ products: data, loading: false });
}
render() {
if (this.state.loading) {
return (<div>App loading</div>);
}
const childrenWithProps = React.Children.map(this.props.children, child => {
const props = { products: this.state.products };
if (React.isValidElement(child)) {
return React.cloneElement(child, props);
}
return child;
});
return (
<div>
<NavMenu />
{childrenWithProps}
<Footer />
</div>
);
}
}
The routing is made in an App
component:
App.js
export default class App extends Component {
render () {
return (
<Layout>
<Route exact path='/'
component={HomePage}/>
</Layout>
);
}
Hence, I am expecting to
App loading
message while the API call hasn't been madeProducts not loaded yet
message while the prop hasn't been passed to the Layout
childrenProducts loaded!
messageHowever, the application is stuck at step two: the products
prop is never received by the children components. The code compiles, there are no runtime errors, and the back-end Api is triggered and sends a valid response.
Why the product
props will never be available in the render()
method of the child HomePage
component?
Following @Nikita Chayka's answer, the props should be passed at routing:
Layout.js
export class Layout extends Component {
render() {
return (
<div>
<NavMenu />
{this.props.children}
<Footer />
</div>
);
}
}
App.js
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
products: null,
loading: true
};
}
componentDidMount() {
this.populateProductsData();
}
async populateProductsData() {
const response = await fetch('/api/products/all');
const data = await response.json();
this.setState({ products: data, loading: false });
}
render() {
if (this.state.loading)
return (<div>App loading</div>);
return (
<Layout>
<Route exact path='/'
render={(props) => (<HomePage {...props} products={this.state.products}/>)}/>
</Layout>
);
}
}
Your Layout component will pass products prop to Route component, not Home component, basically you will have
<Route products={} component={Home} path="/" exact/>
But you need to pass it down to Home, you could check for ideas here - https://ui.dev/react-router-v4-pass-props-to-components/
EDIT
You should not provide component property to Route, only render.