Reputation: 1142
I have a parent component which renders some child components. The child component should display information passed from the parent component.
A battleship game with multiplayer:
The parent component contains a field of 10x10 up to 10x30 buttons (child components). If a button is pressed a signal with the position of the button get emitted to the api. The api decides if the pressed button needs to change it color.
By updating the state of the parent the child components props will not be updated if the child is created dynamically.
Not possible in my case
I consider the child as dump/presentational Component because it only displays information. Also in my case there are 100-300 Child Components. Redux and React
There are 100-300 Child Components...Don’t Overuse Refs
What should I do? Is there a solution that is not anti-pattern?
Current Code
class Parent extends React.Component {
constructor(props) {
super(props);
this.state={
foo: 'BAR'
}
this.child=undefined;
}
componentWillMount(){
this.child=<Child foo={this.state.foo}/>
}
componentDidMount(){
var that=this
setTimeout(function(){
that.setState({ //it is just here for demonstration
foo: 'FOO'
})
}, 1000);
}
render() {
return (
<div>
Is: {this.child}
Not Dynamic created: <Child foo={this.state.foo}/>
Actual: <p>{this.state.foo}</p>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<p>{this.props.foo}</p>
);
}
}
ReactDOM.render(<Parent />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
Code with approach from @RaghavGarg and @Clinton Blackburn
class Parent extends React.Component {
constructor(props) {
super(props);
this.state={
foo: 'BAR'
}
this.child=undefined;
}
componentDidMount(){
var that=this
setTimeout(function(){
that.setState({ //it is just here for demonstration
foo: 'FOO'
})
}, 1000);
}
renderChild(){
return <Child foo={this.state.foo} />
}
render() {
const child=this.renderChild()
return (
<div>
Is: {child}
Not Dynamic created: <Child foo={this.state.foo}/>
Actual: <p>{this.state.foo}</p>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<p>{this.props.foo}</p>
);
}
}
ReactDOM.render(<Parent />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'/>
Upvotes: 1
Views: 3671
Reputation: 3707
Like @Clinton said, componentWillMount
would be only called once. So you are essentially initiating your this.child
variable, but only updating state, you also need to update the value of the variable.
Since you are just making your dynamic components and saving them in the class instance(like this.child
). I would recommend you to do the same task in componentWillUpdate
. So that whenever your state changes, it will be reflected in your dependent variables. But please make sure not to use setState
inside this lifecycle method.
componentWillUpdate()
is invoked just before rendering when new props or state are being received. Use this as an opportunity to perform preparation before an update occurs.
Also, consider using constructor to initiate state and not use setState
in componentWillMount
.
componentWillMount()
is invoked just before mounting occurs. It is called beforerender()
, therefore callingsetState()
synchronously in this method will not trigger an extra rendering. Generally, we recommend using theconstructor()
instead for initializing state.
Reference:
https://reactjs.org/docs/react-component.html#unsafe_componentwillupdate https://reactjs.org/docs/react-component.html#unsafe_componentwillmount
So now, I am supposing you will be having a DS(maybe an object or a nested array) in the state in which you will be maintaining the status of each box(child component). You can loop over it and provide a unique key
(maybe like {row}-{col}
) to every one of them, now you just need to update the state so to reflect the change in the specific child.
Note: Using the unique key to every child will enable react internally optimize the re-render and will not re-render the child(using unique key), which is not changed. See below code for reference.
this.state = {
boxes: {
1: {
1:{ status: true, /* more data */ },
2:{ status: true, /* more data */ },
},
2: {
1:{ status: true, /* more data */ },
2:{ status: true, /* more data */ },
},
3: {
1:{ status: true, /* more data */ },
2:{ status: true, /* more data */ },
},
4: {
1:{ status: true, /* more data */ },
2:{ status: true, /* more data */ },
}
}
};
// this will create a 4x2 box, now you will render using above object
// ...somewhere in your render method
{
Object.keys(this.state.boxes).map(row => (
<div key={`row-${row}`} className="row">
{
Object.keys(this.state.boxes[row]).map(column => (
<Child
key={`box-${row}x${column}`}
data={this.state.boxes[row][column]}
/>
))
}
</div>
))
}
Now, whenever you change the status
of say 2x1
box to false
, react will only re-render the child with key box-2x1
.
shouldComponentUpdate
should be used to decide whether you want to update the component upon the state changes. It's a lifecycle method of React component. By default it returns true
, but you can return false
basis on your condition to not update the component. It would definitely help in maintaining the good performance.
Reference: React: Parent component re-renders all children, even those that haven't changed on state change
Upvotes: 2
Reputation: 436
componentWillMount
is only called once when the parent component is inserted into the DOM. It is probably not where you want to create child components that need to be changed after mounting.
See the component lifecycle at https://reactjs.org/docs/react-component.html#the-component-lifecycle.
The simplest solution is to create the child component in the render()
method. The child component will be re-created when the parent component is updated.
Upvotes: 1