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.
Related
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 = ...
}
I just wonder if it is good that the child component updates the parent component.
in the source code, like following
class Parent extends React.Component{
state = {
name : ''
}
changeState = ((state) => {
this.setState(state)
})
submit = (() => {
// send state to api..
})
render(){
return(
<div>
<Child changeState={this.changeState} {...this.state}/>
<button onClick={this.submit} />
</div>
)
}
}
class Child extends React.Component{
change = ((e) => {
this.props.changeState({
name : e.target.value
})
})
render(){
return(
<input onChange={this.change} value={this.props.name} />
)
}
}
the reason I use this way is submitting method.
There are many input tags, and I want to bind them all together.
but I'm not sure this way is good or not.
because when I type something, parent component always will rerender.
I think it is not good.(actually it just my thinking...)
is it right?
I have used this way to update state of a parent from a child. It does work properly. But it makes the components little complex.
In your case (assuming you do this for text input elements) I don't think this will be a good practice if you are doing it for tiny input components. Because every time you hit a key on a keyboard the parent component will try to update.
But if you are wrapping a set of input elements and pass a larger object to a parent component I think that will be fine.
You could use react life cycle method shouldComponentUpdate() method to control the rendering of the parent component
shouldComponentUpdate
https://reactjs.org/docs/react-component.html#shouldcomponentupdate
shouldComponentUpdate(nextProps, nextState) {
if (this.props.name != nextProps.name) {
return true;
} else {
return false;
}
}
Here nextProps refers to the props you receive(updates) and you can refer to current prop values by "this.props"
And return true to render and false to skip the rendering.
If you have perform validations when the user inputs, then its ok.
Otherwise change 'onChange' event to 'onBlur'
Its a good idea to lift the state up and update it in the parent if multiple other siblings want to refer to the same values. You can optimise on this my making your Parent and Child Components pure as long as they don't have complex and deeply nested props and states.
According to the React docs:
React.PureComponent is exactly like React.Component, but
implements shouldComponentUpdate() with a shallow prop and state
comparison. If your React component’s render() function renders the
same result given the same props and state, you can use
React.PureComponent for a performance boost in some cases.
Re-rendering of parent is not a problem as long as it is not wasted. And Unless you are using Redux, I think this is a proper way to manage the state, i.e., inside the parent component and updating it using the child. In this way, you have made your form into a controlled component.
I think the following page will be useful to you: https://scotch.io/courses/getting-started-with-react/parent-child-component-communication
I am trying to force a child component to re-render. I have tried this.forceUpdate();, but it does not work. I put console.log statements in my <PostList /> component, and none of them are ever called--not componentDidMount, nor componentWillMount, componentWillReceiveProps, none of them. It's as if the <PostList /> component is never initialized. I am sure it is though, because I know for a fact items.count retrieves my items. Here is my render method:
render() {
const items = this.state.posts;
const postList = items.count > 0 ? (<PostList comingFromSearch={true} xyz={items} />) : (<div></div>)
const navBar = <NavigationBar />
return (
<div><br/>{navBar}
<div className="container">
<h3>Search Results for {this.state.searchTerm}</h3>
<div className="row">
<div className="col-x-12">{postList}</div>
</div>
</div>
</div>
)
}
And here is my api call:
retrieveSearch(term) {
Helpers.searchWithTerm(term).then((terms) => {
const postsWithTermsInTitle = terms.titleResults
this.setState({posts: postsWithTermsInTitle})
this.forceUpdate();
}).catch((error) => {
console.log("error searching: " + error);
})
}
I should note, on my previous page, i had another ` component, and maybe react is using that one instead of this one? I want to force it to use this instance.
If this.forceUpdate(); does not make the whole DOM re-render, how can I do that?
thanks
your PostList and NavigationBar Components might not update because they only update when their props are changed (shallow compare).
PostList might not update when changing the inner content of the array, because the component will shallow compare the new state with the previous one. Shallow comparing an array will basically checked against its length property. which does not change in this case.
Quick Solution
Sometimes you need to update a List, without changing any of its props or the length of the list. To achieve this, just pass a prop to the component and keep incrementing it instead of calling force update.
retrieveSearch(term) {
Helpers.searchWithTerm(term).then((terms) => {
const postsWithTermsInTitle = terms.titleResults
this.setState((curState) => ({posts: postsWithTermsInTitle, refreshCycle: curState.refreshCycle+1}))
this.forceUpdate();
}).catch((error) => {
console.log("error searching: " + error);
})
}
render() {
...
<PostList
...
refreshCycle={this.state.refreshCycle}
/>
...
}
Right solution
The right solution is to provide an itemRenderer which you is a function that returns the an individual item from the list. This function is passed as a prop to the component.
This way you have control over how the items inside the list will appear, also changes inside the itemRenderer function will cause a component update.
itemRenderer(itemIndex) {
return <div>{this.props.item[itemIndex]}</div>;
}
render() {
...
<PostList
itemRenderer={this.itemRenderer.bind(this)}
itemsLength={items.length}
/>
...
}
The itemRenderer will be called inside the PostList in a loop (of length itemsLength). each loop will be passed the index of the current iteration, so you can know which item of the list to return from the function.
This way you can also make your list more scalable and more accommodating.
You can check an implementation of such solution on a list package like this one: https://www.npmjs.com/package/react-list
You can force a re-render of a component and all its children by changing the state of the component. In the constructor add a state object:
constructor(props) {
super(props)
this.state = {
someComponentState: 'someValue'
}
}
Now whenever you do:
this.setState(someComponentState, 'newValue')
It will re-render the component and all its children.
This of course assumes your component is a class based component, not a functional component. However, if your component is a functional component you can easily transform it to a class based component as follows:
class ComponentName {
constructor() {
// constructor code
}
render() {
// render code
}
}
export default ComponentName
Understand that componenet level state is not the same as redux state but is exposed only inside the component itself.
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 ^^
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 :)