1

In my code, app is the highest (functional) component. It renders piggybank (which is a class component) which renders piggychild (which is a functional component).

Each component 'console.logs' the name of itself (in the render method for class-component; simply in the function for a functional component) and has a 'mount' function (for the class-component, this means a componentDidMount method; for functional component, this means a useEffect hook whose callback's 2nd parameter is an empty array) which logs 'mount' to the console.

enter image description here


As you can see in the picture, PiggyBank's mount fires first, then PiggyChild's, then App's.

Is there a rule in React that governs the order in which components' 'mount' functions occur?

Or is it enough to just know that mount functions occur after all components are rendered?

tonitone120
  • 1,920
  • 3
  • 8
  • 25
  • Yeah I'm curious about that too. But maybe the useEffect return function does not work exactly the same as the componentDidMount function in terms of order. So maybe the test would be more accurate using only one type of component. – EVeras Oct 30 '20 at 19:18

2 Answers2

2

useEffect has a different timing than componentDidMount. The hook that's closest to CDM is actually useLayoutEffect.

Here's a bit from ReactTraining's blog on useEffect:

They run at different times

First, let's talk about the timing of each. componentDidMount runs after the component mounts. As the docs say, if you set state immediately (synchronously) then React knows how to trigger an extra render and use the second render's response as the initial UI so the user doesn't see a flicker. Imagine you need to read the width of a DOM element with componentDidMount and want to update state to reflect something about the width. Imagine this sequence of events:

  1. Component renders for the first time.
  2. The return value of render() is used to mount new DOM.
  3. componentDidMount fires and sets state immediately (not in an async callback)
  4. The state change means render() is called again and returns new JSX which replaces the previous render.
  5. The browser only shows the second render to avoid flicker.

It's nice that this is how it works for when we need it. But most the time we don't need this pre-optimized approach because we're doing asynchronous network calls and then setting state after the paint to the screen.

componentDidMount and useEffect run after the mount. However useEffect runs after the paint has been committed to the screen as opposed to before. This means you would get a flicker if you needed to read from the DOM, then synchronously set state to make new UI.

How do get the old behavior back when we need it?

useLayoutEffect was designed to have the same timing as componentDidMount. So useLayoutEffect(fn, []) is a much closer match to componentDidMount() than useEffect(fn, []) -- at least from a timing standpoint.

Does that mean we should be using useLayoutEffect instead?

Probably not.

If you do want to avoid that flicker by synchronously setting state, then use useLayoutEffect. But since those are rare cases, you'll want to use useEffect most of the time.

I've added an example which has both Layout Effects and normal Effects in the scenario you described:

The results of running this are as follows. The main thing to note with this is that The Layout effects happen in a sensible order with the componentDidMount lifecycle hook, whereas the effects happen later.

App

PiggyBank

PiggyChild

PiggyChild LayoutEffect

PiggyBank Mount

App LayoutEffect

PiggyChild Effect

App Effect

const {useEffect, useLayoutEffect} = React;
function App(){
 console.log('App');
 useLayoutEffect(()=>console.log('App LayoutEffect'),[]);
 useEffect(()=>console.log('App Effect'),[]);

 return <PiggyBank/>;
}
class PiggyBank extends React.Component{
 componentDidMount(){
  console.log('PiggyBank Mount');
 }
 render(){
  console.log('PiggyBank');
  return <PiggyChild/>
 }
}
function PiggyChild(){
 console.log('PiggyChild');
 useLayoutEffect(()=>console.log('PiggyChild LayoutEffect'),[]);
 useEffect(()=>console.log('PiggyChild Effect'),[]);
 return <div/>;
}
ReactDOM.render(<App/>,document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"/>

Without the LayoutEffects, the order will be:

  1. The console.logs in the render functions, in order from parent to child, since part of rendering each component is rendering the children

  2. Then the componentDidMount runs immediately after it finishes mounting. If there are multiple components with componentDidMounts or useLayoutEffects, these would be called in order from from child to parent as they finish mounting, but before the paint is committed (as mentioned above).

  3. The useEffects run in order from child to parent as they finished mounting in that order, but they will always run after the componentDidMounts and useLayoutEffects due to the timing mentioned above.

const {useEffect} = React;
function App(){
 console.log('App');
 // useLayoutEffect(()=>console.log('App LayoutEffect'),[]);
 useEffect(()=>console.log('App Effect'),[]);

 return <PiggyBank/>;
}
class PiggyBank extends React.Component{
 componentDidMount(){
  console.log('PiggyBank Mount');
 }
 render(){
  console.log('PiggyBank');
  return <PiggyChild/>
 }
}
function PiggyChild(){
 console.log('PiggyChild');
 // useLayoutEffect(()=>console.log('PiggyChild LayoutEffect'),[]);
 useEffect(()=>console.log('PiggyChild Effect'),[]);
 return <div/>;
}
ReactDOM.render(<App/>,document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"/>
Zachary Haber
  • 10,376
  • 1
  • 17
  • 31
  • It's complicating things too much adding `useLayoutEffect`. I'd like if your example just included `componentDidMount` & `useEffect` for `App`, `PiggyBank` & `PiggyChild`. And then explained why – tonitone120 Nov 24 '20 at 13:33
  • That's the thing though, useLayoutEffect runs at the same timing as componentDidMount. Anyways, I added what you've asked for. – Zachary Haber Nov 24 '20 at 15:05
0

Actually it makes absolute sense, and order of useEffect in hierarchy is the same as in componentDidMount. The thing is that componentDidMount fires when all children mounted (so you would see first child event, and only then parent one) - so same order as you see.

For more details about order of componentDidMount you could see here - Order of componentDidMount in React components hierarchy

Nikita Chayka
  • 2,057
  • 8
  • 16
  • You might've misread the results I got. When it comes the 'mounting', I get middle; bottom; top. The article you link only talks about class components whereas my example features a mix of class & functional components – tonitone120 Nov 24 '20 at 13:35