I have 2 components for demonstration of my problem:
Parent:
import React from "react";
import ReactDOM from "react-dom";
import { Grid, Row } from "react-flexbox-grid";
import Hello from "./Hello";
class App extends React.Component {
state = {
name: "Michal"
};
componentDidMount = () => {
this.setState({ name: "Tina" });
};
componentDidUpdate(prevState) {
console.log("App componentDidUpdate", prevState, this.state);
}
handleUpdate = value => {
console.log("App handleUpdate");
this.setState({ name: value });
};
render() {
return (
<Grid>
<Row>
<Hello name={this.state.name} update={this.handleUpdate} />
</Row>
</Grid>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
Child:
import * as React from "react";
class Hello extends React.PureComponent {
componentDidMount() {
// setTimeout(() => {
this.props.update("Matus");
// }, 0);
}
componentDidUpdate(prevProps) {
console.log("Hello componentDidUpdate", prevProps, this.props);
}
render() {
return <h1>Hello {this.props.name}!</h1>;
}
}
export default Hello;
In child component I want to set value in parent state via props function. But setState function is ignored, it works if props function is called from setTimeout.
Can you explain me why it work in setTimeout, why I should avoid this construction. And what is correct way to do it?
Hello component represent "Select", which in componentDidMount will fetch options and set default value.
Thank you.
Components initialise from the bottom up in React. So in your example Hello
triggers componentDidMount
, attempts to set the state in App via this.props.update
, then App
overrides it a split-second later when it calls its own componentDidMount
. The name you set in the child component never reaches the state.
I'm not sure what the purpose of this is, hopefully only for leaning purposes as components shouldn't need to immediately set their own state when mounting. If you need to perform some logic before initialising the state in App
you can use a constructor
and do it there.
Regardless, the solution is remove the initial state setter in App
.
It is not ignored and it does fire. You are just not observing it with your logs. Check out:
https://codesandbox.io/s/kind-jackson-b2r2b?file=/src/App.js
In the console you will see the following execution order in the console window:
Hello componentDidMount props = Object {name: "Michal", update: function ()}
App handleUpdate value = Matus
App componentDidMount props = Object {}
Hello componentDidUpdate props = Object {name: "Tina", update: function ()}
App componentDidUpdate state = Object {}
Object {name: "Tina"}
Thus you will see the child componentDidMount
fires and completes mount before the parent component completed and fires its componentDidMount
, as components completes mounting from the child components up.
So you just never observe the state going to Matus because it triggers a new state change to Tina when it completes mounting.
You setState
function from Hello component is ignored because of the React lifecycle. Basically App componentDidMount
function overrides your state change from Hello component before it was rendered. That's why setTimeout
helps, it moves your state change to the new rendering loop.
I don't know exact why you are trying to load data and pass it from the child component to parent but the good practice in React is to pass data from top to bottom. So the better solution would be to use Select
component to just render the data from parent and react to user events.
<Select options={options} selected={option} handle={handleSelect} />
Reason:
Now,
componentDidMount() {
this.props.update("Matus");
}
Is executed first, which sets the name Matus . Then the following executes -
componentDidMount = () => { this.setState({ name: "Tina" }); };
This sets the name Tina .
All of this happens on the first call-stack where the rendering happens. If we use setTimeout() , then
this.props.update("Matus");
will be moved to the second call-stack, which will be executed after the initial rendering and mounting has ended, thus setting the name Tina and triggering a re-render.
If you want to use class components, you need to use a constructor function to initialise state and pass the props from parent to child.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "Michal"
};
}
// ... rest of parent component
import * as React from "react";
class Hello extends React.PureComponent {
constructor(props) {
super(props)
}
componentDidMount() {
// setTimeout(() => {
this.props.update("Matus");
// }, 0);
}
componentDidUpdate(prevProps) {
console.log("Hello componentDidUpdate", prevProps, this.props);
}
render() {
return <h1>Hello {this.props.name}!</h1>;
}
}
export default Hello;
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.