Skip to main content
seanmcp.com

Render and effect call order in React


Setup: Let’s say I have a React tree like this:

function Parent() {
  console.log("Parent render");
  useEffect(() => {
    console.log("Parent effect");
  }, []);
  useLayoutEffect(() => {
    console.log("Parent layout effect");
  }, []);

  return <Child />;
}

function Child() {
  console.log("Child render");
  useEffect(() => {
    console.log("Child effect");
  }, []);
  useLayoutEffect(() => {
    console.log("Child layout effect");
  }, []);

  return null;
}

ReactDOM.createRoot(/* ... */).render(<Parent />);

What order would you expect the logs to fire in? If you were like me, you’d probably have some mental model of how React works and be able to make an educated guess. But I was wrong.

Then I asked this question to a handful of seasoned React developers, and they all got it wrong.

Because the real answer is unexpected:

Parent render
Child render
Child layout effect
Parent layout effect
Child effect
Parent effect

Try it yourself: Here’s a StackBlitz in case you don’t believe me.

Attempting an explanation: When rendering a tree, React calls the components from the top of the tree downward. So because Parent is at the top of the tree, its render log is called before Child. So far, so good.

Effects are a different story. React effectively queues effects from the bottom of the tree upward, so Child’s effects are called before Parent’s. Plus there is the additional layer that useLayoutEffect is called before useEffect (which, to be fair, does make sense but is one more thing to think about).

I ran into this issue at work when a child had code running on render that was occurring before the parent’s useLayoutEffect ran. There were several layers of components between the two, and finding the cause was not fun.

Takeaway: I don’t expect you or me or anyone else to remember this order. But if you can remember that the order is unexpected, then that will put you on the right path.