Reputation: 11423
If I have Two Components (Parent & Child) like this :
1-The Parent (Countdown):
var Countdown = React.createClass({
getInitialState: function(){
return{count: 0};
},
handleSetCountdown: function(seconds){
this.setState({
count: seconds
});
},
render:function(){
var {count} = this.state;
return(
<div>
<Clock totalSeconds={count}/>
<CountdownForm onSetCountdown={this.handleSetCountdown} />
</div>
);
}
});
module.exports =Countdown;
2-The Child (CountdownForm):
var CountdownForm = React.createClass({
onSubmit: function(e){
e.preventDefault();
var strSeconds = this.refs.seconds.value;
if(strSeconds.match(/^[0-9]*$/)){
this.refs.seconds.value ='';
this.props.onSetCountdown(parseInt(strSeconds,10));
}
},
render: function(){
return(
<div>
<form ref="form" onSubmit={this.onSubmit} className="countdown-form">
<input type="text" ref="seconds" placeholder="Enter Time In Seconds"/>
<button className="button expanded">Start</button>
</form>
</div>
);
}
});
module.exports = CountdownForm;
I'm confused about the life cycle (the order in which the components are rendered)?
Upvotes: 12
Views: 11506
Reputation: 41750
With functional components like this one...
const MyComponent = () => {
useEffect(() => {
trackingFn("useEffect MyComponent");
subscriptions.push(() => { trackingFn("subscription MyComponent") })
return () => trackingFn("useEffect cleanup MyComponent");
})
useLayoutEffect(() => {
trackingFn("useLayoutEffect MyComponent");
return () => trackingFn("useLayoutEffect cleanup MyComponent");
})
try {
trackingFn("render MyComponent");
return <MyChildComponent />
} finally {
trackingFn("finally MyComponent");
}
}
The order is
This is shown by this test.
import { act, render } from "@testing-library/react";
import { useEffect, useLayoutEffect } from "react";
it("should call the methods in the expected order", async () => {
const trackingFn = jest.fn();
const subscriptions: (() => void)[] = [];
const MyGrandChildComponent = () => {
useEffect(() => {
trackingFn("useEffect MyGrandChildComponent");
subscriptions.push(() => { trackingFn("subscription MyGrandChildComponent") })
return () => trackingFn("useEffect cleanup MyGrandChildComponent");
})
useLayoutEffect(() => {
trackingFn("useLayoutEffect MyGrandChildComponent");
return () => trackingFn("useLayoutEffect cleanup MyGrandChildComponent");
})
try {
trackingFn("render MyGrandChildComponent");
return <div />
} finally {
trackingFn("finally MyGrandChildComponent");
}
}
const MyChildComponent = () => {
useEffect(() => {
trackingFn("useEffect MyChildComponent");
subscriptions.push(() => { trackingFn("subscription MyChildComponent") })
return () => trackingFn("useEffect cleanup MyChildComponent");
})
useLayoutEffect(() => {
trackingFn("useLayoutEffect MyChildComponent");
return () => trackingFn("useLayoutEffect cleanup MyChildComponent");
})
try {
trackingFn("render MyChildComponent");
return <MyGrandChildComponent />
} finally {
trackingFn("finally MyChildComponent");
}
}
const MyComponent = () => {
useEffect(() => {
trackingFn("useEffect MyComponent");
subscriptions.push(() => { trackingFn("subscription MyComponent") })
return () => trackingFn("useEffect cleanup MyComponent");
})
useLayoutEffect(() => {
trackingFn("useLayoutEffect MyComponent");
return () => trackingFn("useLayoutEffect cleanup MyComponent");
})
try {
trackingFn("render MyComponent");
return <MyChildComponent />
} finally {
trackingFn("finally MyComponent");
}
}
const { unmount } = render(<MyComponent />);
await act(() => Promise.resolve());
subscriptions.forEach(f => f());
unmount();
expect(trackingFn.mock.calls).toEqual([
['render MyComponent'],
['finally MyComponent'],
['render MyChildComponent'],
['finally MyChildComponent'],
['render MyGrandChildComponent'],
['finally MyGrandChildComponent'],
['useLayoutEffect MyGrandChildComponent'],
['useLayoutEffect MyChildComponent'],
['useLayoutEffect MyComponent'],
['useEffect MyGrandChildComponent'],
['useEffect MyChildComponent'],
['useEffect MyComponent'],
['subscription MyGrandChildComponent'],
['subscription MyChildComponent'],
['subscription MyComponent'],
['useLayoutEffect cleanup MyComponent'],
['useLayoutEffect cleanup MyChildComponent'],
['useLayoutEffect cleanup MyGrandChildComponent'],
['useEffect cleanup MyComponent'],
['useEffect cleanup MyChildComponent'],
['useEffect cleanup MyGrandChildComponent']
])
});
``
Upvotes: 2
Reputation: 101
It is possible to make the parent's ComponentDidMount method execute before the child's ComponentDidMount method. And here is how it is done.
Inside the first div in the parent, before anything is rendered, check if state is set yet. Only render anything if state is set.
Example:
render() {
const { t } = this.props;
return (
<div>
{this.state.row !== null ? (
...
) : null}
</div>
);
}
Upvotes: 0
Reputation: 26144
The render order is executed in the order of the react component tree, however, the mount order is in the reverse order (with the inner-most child component mounting first.)
Upvotes: 1
Reputation: 12216
react-parent-child-lifecycle-order
https://codesandbox.io/s/react-parent-child-lifecycle-order-33qrr
parent constructor
parent WillMount
parent render
child constructor
child WillMount
child render
child DidMount
parent DidMount
parent WillUnmount
child WillUnmount
// child unmount
// parent unmount
Upvotes: 2
Reputation: 5367
Just adding componentWillUnmount to the cycle:
class Child extends React.Component {
constructor(props, ...rest) {
super(props, ...rest);
console.log(this.props.name + " constructor");
}
componentWillMount(...args) {
console.log(this.props.name + " componentWillMount");
}
componentWillUnmount(...args) {
console.log(this.props.name + " componentWillUnmount");
}
componentDidMount(...args) {
console.log(this.props.name + " componentDidMount");
}
render() {
console.log(this.props.name + " render");
return <div>Hi from {this.props.name}!</div>;
}
}
class Parent extends React.Component {
constructor(...args) {
super(...args);
console.log("Parent constructor");
}
componentWillMount(...args) {
console.log("Parent componentWillMount");
}
componentWillUnmount(...args) {
console.log("Parent componentWillUnmount");
}
componentDidMount(...args) {
console.log("Parent componentDidMount");
}
render() {
console.log("Parent render start");
const result =
<div>
<Child name="Child1" />
<Child name="Child2" />
</div>;
console.log("Parent render end");
return result;
}
}
class ParentWrapper extends React.Component {
constructor(...args) {
super(...args);
this.state = { showParent: true };
setTimeout(() => { this.setState({ showParent: false }) });
}
render() {
return <div>{this.state.showParent ? <Parent /> : ''}</div>;
}
}
ReactDOM.render(<ParentWrapper />, document.getElementById("react"));
.as-console-wrapper {
max-height: 100% !important;
}
<div id="react"></div>
<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>
result :
Parent constructor
Parent componentWillMount
Parent render start
Parent render end
Child1 constructor
Child1 componentWillMount
Child1 render
Child2 constructor
Child2 componentWillMount
Child2 render
Child1 componentDidMount
Child2 componentDidMount
Parent componentDidMount
Parent componentWillUnmount
Child1 componentWillUnmount
Child2 componentWillUnmount
Upvotes: 6
Reputation: 1075755
I'm not immediately seeing a clear "this is the order of lifecycle events between parent and child" in the React docs, though I could be missing it.
It's trivial to determine empirically, of course:
class Child extends React.Component {
constructor(...args) {
super(...args);
console.log("Child constructor");
}
componentWillMount(...args) {
console.log("Child componentWillMount");
}
componentDidMount(...args) {
console.log("Child componentDidMount");
}
render() {
console.log("Child render");
return <div>Hi there</div>;
}
}
class Parent extends React.Component {
constructor(...args) {
super(...args);
console.log("Parent constructor");
}
componentWillMount(...args) {
console.log("Parent componentWillMount");
}
componentDidMount(...args) {
console.log("Parent componentDidMount");
}
render() {
console.log("Parent render start");
const c = <Child />;
console.log("Parent render end");
return c;
}
}
ReactDOM.render(<Parent />, document.getElementById("react"));
.as-console-wrapper {
max-height: 100% !important;
}
<div id="react"></div>
<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>
That shows us the order:
Parent constructor Parent componentWillMount Parent render start Parent render end Child constructor Child componentWillMount Child render Child componentDidMount Parent componentDidMount
Which got me wondering about the order of children within a parent, so:
class Child extends React.Component {
constructor(props, ...rest) {
super(props, ...rest);
console.log(this.props.name + " constructor");
}
componentWillMount(...args) {
console.log(this.props.name + " componentWillMount");
}
componentDidMount(...args) {
console.log(this.props.name + " componentDidMount");
}
render() {
console.log(this.props.name + " render");
return <div>Hi from {this.props.name}!</div>;
}
}
class Parent extends React.Component {
constructor(...args) {
super(...args);
console.log("Parent constructor");
}
componentWillMount(...args) {
console.log("Parent componentWillMount");
}
componentDidMount(...args) {
console.log("Parent componentDidMount");
}
render() {
console.log("Parent render start");
const result =
<div>
<Child name="Child1" />
<Child name="Child2" />
</div>;
console.log("Parent render end");
return result;
}
}
ReactDOM.render(<Parent />, document.getElementById("react"));
.as-console-wrapper {
max-height: 100% !important;
}
<div id="react"></div>
<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>
Which gives us:
Parent constructor Parent componentWillMount Parent render start Parent render end Child1 constructor Child1 componentWillMount Child1 render Child2 constructor Child2 componentWillMount Child2 render Child1 componentDidMount Child2 componentDidMount Parent componentDidMount
Not at all surprising, but good to double-check. :-)
Upvotes: 36