How to preserve the state for a "just hidden" component - reactjs

Through my previous question I learned that React preserves the state for child components automatically and that's why its documentation says:
What Shouldn't Go in State?
React components: Build them in render() based on underlying props and state.
Now my question is how to do the same for components that we are just hiding?
Consider a child component that in an iteration is not going to be shown and at the same time we would like to preserve its state for the future when it is going to be brought back!
To illustrate what I exactly mean I've designed a case scenario to show it to you. In the following code you can add stateful child components. A checkbox is provided for each component so you can flag them. Finally the third button will hide the child components that are not flagged. I'm looking for a way to restore the state for the not-flagged components once they are brought back.
Executable code
class BlackBox extends React.Component
{
constructor() {
super();
this.state = {
checked: false,
counter: 0,
};
}
increment = () => {
this.setState(Object.assign({}, this.state, { counter: this.state.counter+1 }));
};
switch = () => {
this.setState(Object.assign({}, this.state, { checked: !this.state.checked }));
};
isChecked() {
return this.state.checked;
}
render() {
return (
<span onClick={this.increment} title={this.props.id} style={{
fontSize: '24pt',
border: '1px solid black',
margin: 10,
padding: 10,
}}>
<input type="checkbox" onChange={this.switch} checked={this.state.checked} />
{this.state.counter}
</span>
);
}
}
class RedBox extends React.Component
{
constructor() {
super();
this.state = {
checked: false,
counter: 0
};
}
increment = () => {
this.setState(Object.assign({}, this.state, { counter: this.state.counter+1 }));
};
switch = () => {
this.setState(Object.assign({}, this.state, { checked: !this.state.checked }));
};
isChecked() {
return this.state.checked;
}
render() {
return (
<span onClick={this.increment} title={this.props.id} style={{
fontSize: '24pt',
border: '1px solid red',
margin: 10,
padding: 10,
}}>
<input type="checkbox" onChange={this.switch} checked={this.state.checked} />
{this.state.counter}
</span>
);
}
}
class Parent extends React.Component {
static blackCount = 0;
static redCount = 0;
state = {
childCmps: [],
showOnlyChecked: false,
};
constructor(props, context) {
super(props, context);
}
addBlackBox = () => {
this.setState(Object.assign({}, this.state, {
childCmps: [...this.state.childCmps, { Component: BlackBox, id: "black" + (++Parent.blackCount) }],
}));
};
addRedBox = () => {
this.setState(Object.assign({}, this.state, {
childCmps: [...this.state.childCmps, { Component: RedBox, id: "red" + (++Parent.redCount) }],
}));
};
showHide = () => {
this.setState(Object.assign({}, this.state, {
showOnlyChecked: !this.state.showOnlyChecked,
}));
};
render() {
let children = this.state.childCmps.map(child => <child.Component key={child.id} id={child.id} ref={child.id} />);
return (
<div>
<button onClick={this.addBlackBox}>Add Black Box</button>
<button onClick={this.addRedBox}>Add Red Box</button>
<button onClick={this.showHide}>Show / Hide Unchecked</button>
<br /><br />
{children.filter(box => !this.state.showOnlyChecked || this.refs[box.props.id].isChecked())}
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById("root")
);

The short answer:
There is no solution that only has advantages and no drawbacks (in real life as well as in your question).
You really have only 3 options here:
use parent state and manage your data (in parent component and/ or stores)
use child state and hide children you do not need (so find another animation solution)
use child state and accept that state is lost for children not re-rendered -
You may want to check out redux for using stores.
The long answer
If you want to
remove a child from the DOM (e.g. for animation purposes with ReactCSSTransitionGroup)
AND keep the checked/unchecked & counter info
Then you cannot keep this in child state. State is by definition bound to the lifecycle of a component. If it is removed from DOM (= unmounted), then you will loose all the state info.
So if you want to save this info, you have to move this info to the state of the parent component (for each child obviously).
You can find a working codepen here.
Some other notes about this code:
Your child <...Box> components now become pure = stateless components
child components call a method on the parent, to update checked state or counter increment. In these methods they pass their own ID
Better not to store entire components in state, but only the props that determine the components.
So the new parent state contains an array of objects, and each object has relevant prop data
In render of the parent: better to filter the components first, before creating an array of components to be rendered
In parent you need new methods (onCheck() and onClick()) that will update the specific object in the array from state. Requires some special fiddling to ensure that we do not directly mutate state here.
for setState(), you do not need to do an Object.assign() and pass in all of state again. If you provide an object with only the parameters that changed, react will leave the other parameters untouched.

rather than removing the component from the DOM entirely (as you are doing through filter, just hide it. Replace children.filter with the following:
{children.map((box, idx) => {
var show = !this.state.showOnlyChecked || this.refs[box.props.id].isChecked();
return <span key={idx} style={{display: (show? 'inline-block': 'none')}}>{box}</span>;
})}

Related

How to pass an onMouseEnter function into child component

I would like to update my parent component's state when hovering over a child component. Basically, this parent component consists of an h1 and four components called Box. Each component has a title prop, which the component renders internally. What I would like to have happen is when a user hovers over a Box, the hovered state of the parent component changes to the title of the hovered child. Here is essentially what I have right now:
class Home extends React.Component {
constructor(props) {
super(props)
this.state = {
hovered: 'none'
};
this.handleHover = this.handleHover.bind(this);
}
handleHover = (n) => {
this.setState((n) => ({
hovered: n
}));
};
handleHoverOut = () => {
this.setState(() => ({
hovered: 'none'
}));
};
render() {
return (
<h1 className={this.state.hovered} >TITLE TITLE TITLE</h1>
<Box oME={ this.handleHover } oML={ this.handleHoverOut } title='Title1'/>
<Box oME={ this.handleHover } oML={ this.handleHoverOut } title='Title2'/>
<Box oME={ this.handleHover } oML={ this.handleHoverOut } title='Title3'/>
<Box oME={ this.handleHover } oML={ this.handleHoverOut } title='Title4'/>
)
}
class Box extends React.Component {
render() {
return(
<section onMouseEnter={() => { this.props.oME(this.props.title)}} onMouseLeave={() => { this.props.oML()}}>
...
</section>
}
I know this might not be 100% the way to go about it, but I think I'm somewhat on the right track! Please help me try to improve my code here, since I'm still learning the basics of React!
I created codesendbox where you can check the solution.
There were couple of issues in your code that I fixed there. Your state was not being displayed properly as title and there were unneeded callback functions.

React Parent component checkbox state updates with one step delay

I have a Parent component:
import React, { Component } from "react";
import { Button } from "./Button";
export class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
numbers: [],
disabled: false
};
this.setNum = this.setNum.bind(this);
}
setNum(num) {
if (!this.state.numbers.includes(num)) {
this.setState(prevState => ({
numbers: [...prevState.numbers, num]
}));
} else if (this.state.numbers.includes(num)) {
let nums = [...this.state.numbers];
let index = nums.indexOf(num);
nums.splice(index, 1);
this.setState({ numbers: nums });
console.log(this.state.numbers);
}
if (this.state.numbers.length >= 4) {
this.setState({ disabled: true });
} else if (this.state.numbers.length < 4) {
this.setState({ disabled: false });
}
}
render() {
return (
<div className="board-container">
<div className="board">
<div className="row">
<Button
id="1"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="2"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="3"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="4"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
</div>
</div>
</div>
);
}
}
... and a Child component:
import React, { Component } from "react";
export class Button extends Component {
constructor(props) {
super(props);
this.state = {
isChecked: false
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
isChecked: !this.state.isChecked
});
var num = e.target.value;
this.props.onChange(num);
}
render() {
const { isChecked } = this.state;
if (isChecked === true) {
var bgColor = "#f2355b";
} else {
bgColor = "#f7f7f7";
}
let disabled = this.props.disabled;
if (this.props.numbers.includes(this.props.id)) {
disabled = false;
}
return (
<div className="number-container" id="checkboxes">
<label
className={!isChecked && disabled === false ? "num" : "checked-num"}
style={{ backgroundColor: bgColor }}
>
{" "}
{this.props.id}
<input
type="checkbox"
name={this.props.id}
value={this.props.id}
id={this.props.id}
onChange={this.handleChange}
checked={isChecked}
disabled={disabled}
/>
</label>
</div>
);
}
}
Whenever any Button component is clicked, the Parent component gets the child Button's id value and puts it into its numbers state array. Whenever a Button is unchecked, the Parent updates is numbers state by removing the id of the child Button.
If my code is right, the expected behavior is whenever a Button checkbox is clicked, the Parent numbers state will be updated immediately (adding or removing a number). However, it always updates with one step lag behind.
I know, that the issue is dealing with the React states not being updated instantly, and I've checked similar issues on Stackoverflow. The problem is that I can't figure it out how to make this two components interact with each other in a proper way. What would be the solution for this issue?
Here are three screenshots from codesandbox
If you want to play with it please find the link https://codesandbox.io/s/w2q8ypnxjw
What I did was, I basically copied and pasted your code and updated setNum function to reflect the changes Think-Twice suggested
setNum(num) {
if (!this.state.numbers.includes(num)) {
this.setState(
prevState => ({
numbers: [...prevState.numbers, num]
}),
() => {
console.log("state logged inside if", this.state.numbers);
}
);
} else if (this.state.numbers.includes(num)) {
let nums = [...this.state.numbers];
let index = nums.indexOf(num);
nums.splice(index, 1);
this.setState({ numbers: nums }, () => {
console.log("state logged inside else if", this.state.numbers);
});
}
if (this.state.numbers.length >= 4) {
this.setState({ disabled: true });
} else if (this.state.numbers.length < 4) {
this.setState({ disabled: false });
}
}
So before going further let's quickly address a couple of things regarding to React and setState
As B12Toaster mentioned and provided a link which contains a
quote from official documentation
setState() does not always immediately update the component. It may
batch or defer the update until later.
Think-Twice's also points out that by stating
Basically setState is asynchronous in React. When you modify a value
using setState you will be able to see the updated value only in
render..
So if you want to see the immediate state change in a place which
you trigger setState, you can make use of a call back function as
such setState(updater[, callback])
There are two approaches when it comes to and updater with setState,
you could either pass an object, or you could pass a function So in
Think-Twice's example, an object is passed as an updater
this.setState({ numbers: nums } //updater, () => {
console.log(this.state.numbers); //this will print the updated value here
});
When a function is used as an updater (in your setNum function you
already do that), the callback function can be utilized like below
if (!this.state.numbers.includes(num)) {
this.setState(
prevState => ({
numbers: [...prevState.numbers, num]
}),
() => {
console.log("state logged inside if", this.state.numbers);
}
);
}
Your current implementation and communication structure seems fine. It is actually called Lifting State Up which is recommended also by official documentation.
Basically you store the state of array numbers in a parent component (which can be considered as the source of truth) and you pass the method that changes the state as a prop to it's child component.
In the codesandbox link I provided, the functionalities works the way I expect (at least this is what I expect from your code)
Basically setState is asynchronous in React. When you modify a value using setState you will be able to see the updated value only in render. But to see updated state value immediately you need to do something like below
this.setState({ numbers: nums }, () => {
console.log(this.state.numbers); //this will print the updated value here
});

ReactJS: How can I change dynamically inserted Component's data

I'm trying to change children Component to another component by using state. This injects new Component correctly, however, if I want to change its props dynamically, nothing is changing. componentWillReceiveProps isn't triggered.
In my scenario, I'll have many components like TestComponent (nearly 20-30 components) and they all have different HTML layout (also they have sub components, too). I switch between those components by selecting some value from some list.
Loading all those components initially doesn't seem a good idea I think. On the other hand, I haven't found anything about injecting a Component inside main Component dynamically.
Here is a very basic example of what I want to achieve. When clicking on the button, I insert TestComponent inside App. After that, on every one second, I increment a state value which I try to bind TestComponent but, the component value is not updating.
If I use commented snippet inside setInterval function instead of uncommented, it works but I have to write 20-30 switch case for finding the right component in my real code (which I also wrote when selecting a value from list) so, I want to avoid using that. Also, I'm not sure about the performance.
So, is this the correct approach, if so, how can I solve this problem? If it is wrong, what else can I try?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
component: <p>Initial div</p>,
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
this.setState({
component: <TestComponent currentValue={this.state.componentData} />
});
setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
})
// This will update TestComponent if used instead of above
/*this.setState({
componentData: this.state.componentData + 1,
component: <TestComponent currentValue={this.state.componentData} />
});*/
}, 1000)
}
render() {
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{this.state.component}
</div>
)
}
}
class TestComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
currentValue: this.props.currentValue
};
}
componentWillReceiveProps(nextProps) {
this.setState({
currentValue: nextProps.currentValue
});
}
render() {
return (
<p>Current value: {this.state.currentValue}</p>
)
}
}
ReactDOM.render(
<App />
,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" style="width: 200px; height: 200px;"></div>
To dynamically render the child components you can use React.createElement method in parent, which results in invoking different components, this can be used as, below is sample code, hope it helps.
getChildComponent = (childComponentName) => {
const childComponents = {
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
},
componentProps = Object.assign({}, this.props,this.state, {
styles: undefined
});
if (childComponents[childComponentName]) {
return React.createElement(
childComponents[childComponentName],
componentProps);
}
return null;
}
render(){
this.getChildComponents(this.state.childComponentName);
}
Here in the render function, pass the component name, and child will render dynalicaaly. Other way of doing this can be, make childComponents object as array , look below fora sample
const childComponents = [
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
]
Note: You have to import all child components here in parent, these
are not strings.
That's because as Facebook mentions in their React documentation.
When you call setState(), React merges the object you provide into the current state.
The merging is shallow
For further information read the documentation
So for this case the only modified value will be componentData and component won't trigger any updates
Solution
A better case to solve this issue is using Higher-Order components (HOC) so the App component doesn't care which component you are trying to render instead It just receives a component as a prop so you can pass props to this component base on the App state.
Also, you don't need a state in TestComponent since you get the value as a prop and it's handled by App.
I also added a condition to prevent adding multiples setInterval
class App extends React.Component {
interval;
constructor(props) {
super(props);
this.state = {
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
if (!this.interval) {
this.setState({
componentData: this.state.componentData + 1
});
this.interval = setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
});
}, 1000);
}
}
render() {
let Timer = this.props.timer;
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{!this.state.componentData ? <p>Initial div</p> : <Timer currentValue={this.state.componentData} />}
</div>
)
}
}
class TestComponent extends React.Component {
render() {
const { currentValue } = this.props;
return (
<p>Current value: {currentValue}</p>
)
}
}
ReactDOM.render(<App timer={TestComponent} /> ,document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.js"></script>
<div id="app" style="width: 200px; height: 200px;"></div>

Communication between two child components without causing a complete re-render in parent

I've been learning and using ReactJS. After reading numerous threads and blog posts still I'm confused with having effective communication between components in ReactJS.
Let's say I've a parent component "A" and it has numerous child components "a", "b", "c", "d", "e" etc. Now something happened in the "a" component and I want to re-render the "b" component.
Since "a" and "b" has the same parent I can use the parent itself as the communication medium between this two. But the problem here is when I use parent as the communication medium it changes it's state and trigger the re-render and that'll cause the re-rendering of all the components "a", "c", "d", "e" etc. which I don't like it, instead I want to forcefully re-render only the "b" component.
Is this possible? How can I achieve this? Basically I want to do this for performance reasons.
There is one way to do that, but...
JavaScript if fast and React reconciliation algorithm is also very fast. React will not re-render DOM, it will just check if there are changes in other components and apply if there are.
You can make what you want. For that you have shouldComponentUpdate function, which is called before rendering. So if you implement this function for your child components, they will not be re-rendered. However, it is recommended to use this function only of you really have issues with performance.
If you child components are simple and don't have deep trees of objects, you can use shallow compare helper function. There is a probability that in future it will be included in all pure components by default.
It's totally feasible using plain React. Your parent component need to act as a container and hold the state corresponding to the props of its children. When one of its child triggers an update, if you implement the shouldComponentUpdate method other child will not re-render. Here is a working snippet, if you checked / unchecked one of the item in the first list, all three items will re-render. In the second list bellow, only the targeted item will re-render.
class ItemsList extends React.Component {
constructor(props) {
super(props)
this.state = {
items: {
a: false,
b: false,
c: true
}
}
}
render() {
const self = this
return (
<ul>
{Object.keys(self.state.items).map(
id => (
<Item
key={id}
id={id}
checked={self.state.items[id]}
handleChange={(e) => {
e.persist()
this.setState(state => ({
...self.state,
items: {
...self.state.items,
[id]: e.target.checked
}
}))
}}/>
)
)}
</ul>
)
}
}
class Item extends React.Component {
shouldComponentUpdate(nextProps) {
return nextProps.checked != this.props.checked
}
render() {
console.log(`rendering ${this.props.id}`)
return (
<li>
<span>{this.props.id}</span>
<input
type="checkbox"
checked={this.props.checked}
onChange={this.props.handleChange}/>
</li>
)
}
}
class ItemsList2 extends React.Component {
constructor(props) {
super(props)
this.state = {
items: {
a: false,
b: false,
c: true
}
}
}
render() {
const self = this
return (
<ul>
{Object.keys(self.state.items).map(
id => (
<Item2
key={id}
id={id}
checked={self.state.items[id]}
handleChange={(e) => {
e.persist()
this.setState(state => ({
...self.state,
items: {
...self.state.items,
[id]: e.target.checked
}
}))
}}/>
)
)}
</ul>
)
}
}
class Item2 extends React.Component {
render() {
console.log(`rendering ${this.props.id}`)
return (
<li>
<span>{this.props.id}</span>
<input
type="checkbox"
checked={this.props.checked}
onChange={this.props.handleChange}/>
</li>
)
}
}
ReactDOM.render(<ItemsList/>, document.getElementById('app2'))
ReactDOM.render(<ItemsList2/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.0/react-dom.js"></script>
<script src="https://wzrd.in/standalone/react-addons-perf#latest"></script>
<div id="app"></div>
<div id="app2"></div>

How should I be recreating a stateful child component in a parent's render method?

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')
);
Isn't the state lost if I recreate a child component from scratch?
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:
ReactDOM.render(<MyComponent />, div);
ReactDOM.render(<MyComponent />, div);
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 class BlackBox and a unique identifier for the key. (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):
class Parent extends React.Component {
static blackCount = 0;
static redCount = 0;
state = {
childCmps: [],
};
constructor(props, context) {
super(props, context);
}
addBlackBox = () => {
this.setState({
childCmps: [...this.state.childCmps, { Component: BlackBox, id: "black" + (++Parent.blackCount) }]
});
};
addRedBox = () => {
this.setState({
childCmps: [...this.state.childCmps, { Component: RedBox, id: "red" + (++Parent.redCount) }]
});
};
render() {
return (
<div>
<button onClick={this.addBlackBox}>Add Black Box</button>
<button onClick={this.addRedBox}>Add Red Box</button>
<br /><br />
{this.state.childCmps.map(child => <child.Component key={child.id} />)}
</div>
);
}
}
Example in CodePen.
Notes:
I used 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 use Parent.blackCount = 0, etc, to initialize static class properties if you don't have support for class properties.)
I used fat arrow function properties as event handler callbacks to ensure this is in the correct scope. (You can use this.addBlackBox = this.addBlackBox.bind(this) in the constructor if you don't have support for class properties.)
I moved state initialization to a class property. As you can guess, I highly recommend you make use of class properties. :)
I used ES6 spread with array literal initialization to append a new box and create a new array.
Finally, in the Parent/render() function each box component is always re-rendered using a map() of the state with dynamic component type rendering of <child.Component>.
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.
addRedBox() {
let newChildList = this.state.childList.slice();
newChildList.push("Red");
this.setState({
childList: newChildList
});
}
And then in your render() function do this:
{this.state.childList.map(function(color,i) {
if (color=="Black") {
return <BlackBox key={i} />
} else {
return <RedBox key={i} />
}
})}
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
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):
class Parent {
list children
temporarilyAbondonChildren() {
for each child in children {
Orphanage.leaveAtDoorStep( child )
}
doStuffForTenYears()
for each child in Children {
output child.isOk()
}
}
}
And here's the closest equivalent to it in React:
class Parent {
temporarilyAbandonChildren() {
doStuffForTenYears()
child_properties = Orphanage.whatWouldveHappenedToMyKidHadIGivenBirthAndLeftThemForTenYears()
children = renderImaginaryChildren( child_properties )
for each child in children {
output child.isOk()
}
}
}
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!
I agree.

Resources