I have the following reactJS component structure
<Parent>
<Child1/>
</Parent>
<Parent>
<Child2/>
</Parent>
the children have a function that performs different API calls.. Until thats finished, the child is not ready to be rendered. So is there a way for me to have the parent display
"waiting for data..."
and call the method in the child to do the API call
in the child i would like to have a simple render method which does not have to check if the get API call has completed or not
I have tried two approaches but both unsuccessful
Try call a method in the child with out rendering it.. React.Children.map(this.props.children, (child)=>child.doAPICall()) but this child does not seem to have its functions available
Override the render function dynamically so it renders nothing, then after the children have completed the API calls to swap the render mthod back
React.Children.map(this.props.children, (item, i) =>
(React.cloneElement(item, {
render: () => false
})))
this will allow me to override props but not the render method
Any advice would be greatly appriciated
You should do a conditional render. I would suggest in one of the following two ways:
Either in the parent component. Do the necessary API calls to seed the data, and then render the children when the data is ready.
Or in the children. Do the data calls (for example in componentDidMount), and then render the data when it's ready. Until then render something else, ie some text or an image that says 'Loading'.
Whether or not you decide to conditionally render the children from the parent, or if you simply decide to do a conditional render within the children's render themselves, it would look something like this:
render() {
return (
<div>
{this.state.data?
<div>{this.state.data.somedata}</div>
:
<div>Loading...</div>}
</div>
);
}
or even:
render() {
if (!this.state.data) {
return <div>Loading...</div>
}
return (
<div>
<div>{this.state.data.somedata}</div>
</div>
)
}
Finally, an even more concise way to conditionally render is with this syntax:
render() {
return(
<div>
{this.state.data && <div>{this.state.data.someField}</div>}
</div>
);
}
or for example
render() {
return this.state.data && <div>{this.state.data.someField}</div>;
}
.. hopefully you get the idea :)
Related
I'd like to create a reusable component that may contain various children that might require loading data. The parent component doesn't (and should not) know, if any childs need to fetch additional data. It looks like this:
function MyPage(props) {
return (
<>
<WidgetA/>
<WidgetB/>
<WidgetC/>
</>
);
}
Now, instead of showing 3 spinners for each individual widget, I'd like to show just one spinner for the whole page. Also, when there is no data available (none of the childs returned any HTML), I'd like to show another component saying something like "There is no data available".
I've already tried a simple idea, namely to return null from a child to indicate that it isn't ready yet.
function MyPage(props) {
const widgetA = <WidgetA/>
if(!widgetA) {
return <div>Loading</div>
}
return (
<div>
<WidgetA/>
</div>
);
}
function WidgetA() {
// ...
if(loading) return null
}
However, this does not work because I am unable to determine whether a component is returning something or not. The component is never null, React.Children.count doesn't work and so on.
You can use ternary condition for this- Sample app
function MyPage() {
const [isLoading, setLoading] = React.useState(true);
React.useEffect(()=>{
fetch('https://jsonplaceholder.typicode.com/users')
.then(res=>res.json())
.then(res=>{
setLoading(false)
console.log(res)
})
},[])
return (
<div>
{!isLoading ?
<>
<WidgetA/>
<WidgetB/>
<WidgetC/>
</>:<h2>Loading....</h2>
}
</div>
);
}
isLoading will be set true/false as per your condition like fetching api or inside click function you can set true/false.
Live working demo to use loading.
Use some central store management like redux. And call that loading variable in header component which is common for all three child components.
Make api calls from redux and toggle isLaoding variable on api calls.
Your job done.
I have a component with URL like product/:id and it has some children components. When I route and change param id I get data from server again, set state, and re-render the page. But the children components are not re-rendered, they are rendered again ( So now I have twice same HTML Code ).
I don't know why it happened.
This is my children component:
listProductCardHTML = this.state.randomList.map((card, index) => {
return (
<ProductCard cardContent={card} key={index}>
</ProductCard>
)
})
And this is JSX:
<div className="box-product product-carousel" id="related-carousel">
{listProductCardHTML}
</div>
The problem seems that when you get routed to your product/:id, you push a new child to the randomList array.
Therefore the code listProductCardHTML = this.state.randomList.map((card, index) generates multiple JSX elements and are rendered.
Please check you setState method and check if randomList is being set correctly.
I'm new to React and i'm facing an issue related to state updates.
I have a Parent Component. In the Parent Component constructor, i create multiple instance of a Child Component.
Using the state of the Parent Component, i display one of the Child Component instance.
Instances of Child Component have some Parent Component state value passed as props.
The Parent Component state looks like this (i have simplify the code so it can be clearer)
displayedContainer: {...} // An Instance of Child Component
isLoading: false
The Parent Component constructor looks like this
constructor(props) {
super(props);
// Create state
this.state = {
isLoading: false,
displayedContainer: null
};
// Create all Child Component (Container)
this.defaultComponent = <Container
isLoading={this.state.isLoading}
></Container>
// Others Child Component are created the same way as above.
// To clearify the code i have removed them.
}
And here is the render method
render() {
return (
<div>
{this.state.displayedContainer}
<div className="container-left-bar"></div>
</div>
)
}
From there, i can switch from one Child Component display to another so the state.displayedContainer is working. But when the state.isLoading is getting updated, Child Components doesn't detect it. I think it's because i'm creating the Child Component in the constructor.
How should i do if i want to keep the logic of creating Child Components before rendered it but fix the issue of state updates not detected ?
Thanks for the help !
The problem is that you render the <Container /> only once, in the constructor. The rendered instance is in the memory (this.defaultComponent) and therefore when you call this.setState the child never gets updated - is not notified about the change of any prop. This code should go to render() method.
Think of it like this:
When React determines this.setState (e.g. you want to display other container then the current one), React calls render() method, and should rerender <Container .../> with updated props. But since the code for rendering the component is not in the render() method - code that tells the <Container .../> to use newest isLoading prop from the state, <Container /> never really gets updated with new value of the isLoading prop (or any other prop).
You should achieve something like this:
render() {
...
let renderCurrentContainer = null
if (...) {
renderCurrentContainer = <Container isLoading={this.state.isLoading} ...otherPropsHere... />
}
else if (...) {
renderCurrentContainer = ...
}
else if (...) {
renderCurrentContainer = ...
}
return <...>
{renderCurrentContainer}
</...>
}
If you're asking what to put into the if condition, you need to somehow mark which component to render currently, I'll leave that to your creativity, but you can use something like currentRenderedContainerIndex which can have values {0, 1, 2}, or currentRenderedContainer string from enum e.g. {'FIRST_COMPONENT', 'SECOND_COMPONENT', 'THIRD_COMPONENT'}
Then you would go with something like this:
if (currentRenderedContainer === 'FIRST_COMPONENT') {
renderCurrentContainer = <Container isLoading= {this.state.isLoading} ...otherPropsHere... />
}
else if (currentRenderedContainer === 'SECOND_COMPONENT') {
renderCurrentContainer = ...
}
else if (currentRenderedContainer === 'THIRD_COMPONENT') {
renderCurrentContainer = ...
}
Sorry if this is obvious somewhere in the documentation, but I am trying to wait until my state is set on a parent component before rendering a child component:
Paraphrasing:
class Parent extends Component {
componentWillMount() {
firestack.database.ref()
.then((snapshot) => {
this.setState({myVal: snapshot.val})
})
}
render() {
// Renders before request finishes setting state,
// Child component receives an undefined val
return (
<ChildComponent
myVal={this.state.myVal}
/>
)
}
}
My render hits before the request finishes, so I'm not able to pass the new State to the child component's constructor. How can I properly do this?
Hopefully this is low hanging fruit to someone.
First, I recommend moving your async request to componentDidMount. Not mandatory, but it's a better point in the life-cycle. Your component will need to able to handle myVal == undefined anyway.
Then, don't render the child component until myVal is available:
render() {
return (
<div>
{ this.state.myVal && <ChildComponent myVal={this.state.myVal} /> }
</div>
)
}
Or perhaps, render a spinner instead:
render() {
return (
<div>
{ this.state.myVal
? <ChildComponent myVal={this.state.myVal} />
: <Spinner />
}
</div>
)
}
Note:
You can't use if inside JSX, so this not-so-kosher use of && is required to keep the syntax compact. A common pattern.
You can have a this.state.isReady boolean instead of asking for the presence of a specific value. Another common pattern.
If ChildComponent is the only element you're going to render, you may not need the <div /> wrapper, but you usually do for some reason or other.
If the myVal parameter is a required for ChildComponent, you just need to do this in parent:
return (
{this.state.myVal !== undefined &&
<ChildComponent
myVal={this.state.myVal}
/>
}
)
Then react will render empty string at first time and after changing state it will render again with filled myVal value.
Also, as it was mentioned in another answer, it's much better to use componentDidMount.
It's recommend to do your API calls within componentDidMount rather than componentWillMount. https://facebook.github.io/react/docs/react-component.html#componentdidmount
Setting state in this method will trigger a re-rendering.
I'm learning React and I've started extracting components. I understand how to bind an event like onClick on a child component, but what about a grandchild?
Example:
I have a List component. It has ListRow children. Within each ListRow child, I have a button component for deleting that particular row from the parent (List). My thoughts are that thedeleteRowclick handler would be on theListcomponent so that I could then set the state. However, I can't seem to find a way to call the grandparent's (List`) eventHandler.
<List /> // has event handler as well as state for the list items
<ListRow />
<DeleteButton /> //when clicking this i want to delete parent <ListRow />
Am I just supposed to pass the onclick down the chain?
When creating components you have to decide whether or not a component is a functional component or a component that needs to manage state. Here is an example where you have a "Grandparent" that passes down functionality to it's child and the child to it's child. If a component does not need to manage state you make it a "functional component" like the "Parent" and "Child" examples below:
class GrandParent extends Component {
handleState = (obj) => {
this.setState(obj);
}
render() {
return (
<Parent handleState={this.handleState} />
);
}
}
function Parent(props) {
render() {
return (
<Child handleState={props.handleState} />
);
}
}
function Child(props) {
render() {
return (
...
);
}
}
You want to pass it down along and wherever you need to call the function you can use it as props.handleState() from whatever component that you send it to.
You could try something this:
// grandparent function that goes into parent
heirloom()
{
console.log("grandparent says hi");
//something happens
}
// everything else (put into all subsequent children)
heirloom()
{
this.props.heirloom();
}
<List heirloom="this.heirloom">
<ListRow heirloom="this.heirloom" />
<DeleteButton onClick="this.heirloom"/>
My syntax may be off and this may or may not work, I haven't had the chance to play around with React for a while. If it does, great! If it doesn't, let's just hope someone with a better answer comes along ^^