skip to Main Content

In React, if a child component and a parent component both have useEffect hooks, why does the child component’s useEffect hook execute before the parent’s?

import React, { useState, useEffect } from "react";

function Parent() {

  useEffect(() => {
    console.log("Parent useEffect");
    // Perform some side effect logic
  }, []);



  return (
    <div>
      <h1>Parent Component</h1>
      <Child/>
    </div>
  );
}

function Child() {
  useEffect(() => {
    console.log("Child useEffect");
    // Perform some side effect logic
  }, []);

  return (
    <div>
      <h2>Child Component</h2>
    </div>
  );
}

export default function App() {
  return (
    <div className="App">
      <Parent />
    </div>
  );
}

In React, if a child component and a parent component both have useEffect hooks, I expect it is possible to make the parent component’s useEffect hook execute before the child’s.

3

Answers


  1. I think that you could do something like the following:

    const { useState, useEffect } = React;
    
    function Parent() {
      const [parent_useEffect_done, set_parent_useEffect_done] = useState(false);
      
      useEffect(() => {
        console.log("Parent useEffect");
        // Perform some side effect logic
        set_parent_useEffect_done(true);
      }, []);
    
      return (
        <div>
          <h1>Parent Component</h1>
          <Child parent_useEffect_done={parent_useEffect_done} />
        </div>
      );
    }
    
    function Child({ parent_useEffect_done }) {
      useEffect(() => {
        if(parent_useEffect_done) {
          console.log("Child useEffect");
        }
        
        // Perform some side effect logic
      }, [parent_useEffect_done]);
    
      return (
        <div>
          <h2>Child Component</h2>
        </div>
      );
    }
    
    function App() {
      return (
        <div className="App">
          <Parent />
        </div>
      );
    }
    
    const root = ReactDOM.createRoot(document.getElementById('app'));
    root.render(<App />);
    <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
    
    <div id="app"></div>

    This records whether the parent has completed its useEffect in state, passes that down to the child, uses the child’s useEffect dependency array to decide whether to run or not.

    For your original example I think we can check that it is running the child useEffect first:

    const { useState, useEffect } = React;
    
    let effect_count = 0;
    
    function Parent() {
      useEffect(() => {
        console.log("Parent useEffect " + ++effect_count);
        // Perform some side effect logic
      }, []);
    
      return (
        <div>
          <h1>Parent Component</h1>
          <Child />
        </div>
      );
    }
    
    function Child() {
      useEffect(() => {
        console.log("Child useEffect " + ++effect_count);
        
        // Perform some side effect logic
      }, []);
    
      return (
        <div>
          <h2>Child Component</h2>
        </div>
      );
    }
    
    function App() {
      return (
        <div className="App">
          <Parent />
        </div>
      );
    }
    
    const root = ReactDOM.createRoot(document.getElementById('app'));
    root.render(<App />);
    <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
    
    <div id="app"></div>
    Login or Signup to reply.
  2. Edit:

    According to the React documentation, the useEffect is called during the mount: https://react.dev/reference/react/useEffect. The parent useEffect should be called before the child one. The reason you see the console of child one is probably because of the console.log which is an async function: https://nodejs.org/api/async_hooks.html#printing-in-asynchook-callbacks

    Not sure it exists a real "clean" solution for that. What would be your use case?

    A workaround would be to render conditionally the child so the condition is the execution of the parent useEffect

    import React, { useState, useEffect } from "react";
    
    function Parent() {
      const [triggered, setTriggered] = useState(false)
      useEffect(() => {
        console.log("Parent useEffect");
        // Perform some side effect logic
    
       setTriggered(true);
      }, []);
    
      return (
        <div>
          <h1>Parent Component</h1>
          {triggered && (
            <Child/>
          )}
        </div>
      );
    }
    
    function Child() {
      useEffect(() => {
        console.log("Child useEffect");
        // Perform some side effect logic
      }, []);
    
      return (
        <div>
          <h2>Child Component</h2>
        </div>
      );
    }
    
    export default function App() {
      return (
        <div className="App">
          <Parent />
        </div>
      );
    }
    
    Login or Signup to reply.
  3. One potential solution is to swap the parent’s useEffect with a useLayoutEffect.

    Example:

    import React, { useEffect, useLayoutEffect } from "react";
    
    function Parent() {
      ...
    
      useLayoutEffect(() => {
        console.log("Parent useEffect");
        // Perform some side effect logic
      }, []);
    
      ...
    }
    
    function Child() {
      ...
    
      useEffect(() => {
        console.log("Child useEffect");
        // Perform some side effect logic
      }, []);
    
      ...
    }
    

    The reason this works is because, as mentioned in the documentation:

    useLayoutEffect is a version of useEffect that fires before the browser repaints the screen.

    This would do the trick, but usually this type of situation points to some antipattern.
    And either way you are creating an implicit dependency here, which is not really advised.
    So I suggest to first re-think this and try to figure out if there is a more straightforward way to achieve whatever it is you’re trying to achieve.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search