Facebook says that I should not keep a React component in its parent's state. Instead I should be recreating the child in render method each time it is run.
What Shouldn't Go in State?
React components: Build them in render() based on underlying props and state.
Now my question is: How can I do that? Is it even possible? Isn't the state lost if I recreate a child component from scratch?
The only way I can think of that this scenario will work in, is that there's only one state object and it belongs to the root component. The rest of components will only have props
and whenever they want to update some state of theirs, they need to call some parent's handler all the way up to root component, since it's the only component with an state object! And once updated, the root will give the child components back their state as props
. Which I don't think it is practical at all!
[UPDATE]
Here's a sample code that I find hard not to store components in the parent's state:
http://codepen.io/mehranziadloo/pen/XdLvgq
class BlackBox extends React.Component
{
constructor() {
super();
this.state = {
counter: 0
};
}
increment() {
this.setState({ counter: this.state.counter+1 });
}
render() {
return (
<span onClick={this.increment.bind(this)} style={{
fontSize: '24pt',
border: '1px solid black',
margin: 10,
padding: 10,
}}>
{this.state.counter}
</span>
);
}
}
class RedBox extends React.Component
{
constructor() {
super();
this.state = {
counter: 0
};
}
increment() {
this.setState({ counter: this.state.counter+1 });
}
render() {
return (
<span onClick={this.increment.bind(this)} style={{
fontSize: '24pt',
border: '1px solid red',
margin: 10,
padding: 10,
}}>
{this.state.counter}
</span>
);
}
}
class Parent extends React.Component
{
constructor() {
super();
this.state = {
childCmps: [],
};
}
addBlackBox() {
let newState = this.state.childCmps.slice();
newState.push(<BlackBox key={newState.length} />);
this.setState({
childCmps: newState
});
}
addRedBox() {
let newState = this.state.childCmps.slice();
newState.push(<RedBox key={newState.length} />);
this.setState({
childCmps: newState
});
}
render() {
let me = this;
return (
<div>
<button onClick={this.addBlackBox.bind(this)}>Add Black Box</button>
<button onClick={this.addRedBox.bind(this)}>Add Red Box</button>
<br /><br />
{this.state.childCmps}
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
You only need to keep in parent's state any data necessary for rendering the children components. Typically, this is just the props you want pass, or the type of component.
In your case, this is just the color of the component "Red" or "Black".
So in parent state, an array containing Strings with value "Red" or "Black" is enough.
And everytime one of the buttons is clicked, you simply add another item to the array, and set state again. Something like this.
And then in your
render()
function do this:On re-render, you simply pass new props (if any) to your child components, and each child component will then also re-render with the new props.
Passing new props to the child will not reset the child component. It will simply run all lifecycle methods again (including
render()
).You can find a working version in this codepen.
the component only renders if the state changes(updates), and you should keep your state simple, use
props
to communicate with the children components.and when your App gets larger you can use Flux or Redux to manage your states
No, because React internally manages the backing instances (which hold the state) and does not replace them if two calls to
render()
say to render that component.In other words:
This will not create
MyComponent
twice, but only once. It will render it twice: the first time it doesn't exist, so it creates it, and the second time it already exists, so it will update it. Any internal state that may be set between the two render passes will be preserved.React is optimized to allow you to simply create complete, declarative render functions, and it figures out what changes are needed to actualize the rendering.
Update
The example you posted is using keys on a dynamic list of children. Keys are a way to identify specific children (and where they exist), so you need to be careful not to change keys between render passes for elements that maintain state.
Instead of storing the actual rendered components in state, such as
<BlackBox key={i} />
, store the necessary data to render the component, such as the component classBlackBox
and a unique identifier for thekey
. (FYI you shouldn't use index as key, since index can change. I recommend using an always incrementing counter.)Here is the
Parent
class modified to work without storing rendered components in state (the other components can remain as is):Example in CodePen.
Notes:
static
(aka global) props to count how many black and red boxes have been added, combined with the strings "red" and "black" to form unique keys. (You can useParent.blackCount = 0
, etc, to initialize static class properties if you don't have support for class properties.)this
is in the correct scope. (You can usethis.addBlackBox = this.addBlackBox.bind(this)
in the constructor if you don't have support for class properties.)state
initialization to a class property. As you can guess, I highly recommend you make use of class properties. :)Parent/render()
function each box component is always re-rendered using amap()
of the state with dynamic component type rendering of<child.Component>
.You are attempting to see an Object-Oriented approach in React. Don't. There's OO and then there's whatever it is that Facebook do.
No, you cannot store components in state, as per the documentation you quoted. You can try it but you'll find things just don't work.
Here's an example of an OO class (in pseudocode):
And here's the closest equivalent to it in React:
}
I agree.