React: useEffect firing in children before parent

Created on 1 Apr 2019  Â·  11Comments  Â·  Source: facebook/react

Do you want to request a feature or report a bug?
Feature (I believe)
What is the current behavior?
Right now, the effects in useEffect fire children-first. I know this makes sense, since the behaviour of the first render has a close correlation to cDm. However, I cannot seem to find a way to fire events parent-first. Before, I could do it with cWm, and after that was deprecated, in the constructor. How can I accomplish that with hooks?
CodeSandbox example:
https://codesandbox.io/s/035lqnozzl?fontsize=14
What is the use case?
Imagine I want to post to an external server when two components were first rendered, to measure a sort of meaningful paint.

- Component A -> post("first render"", component: A)
----
--------
------------ Component B -> post("first render", component: B)

How could I accomplish this with hooks?

Question

Most helpful comment

Can we please elaborate the answer? Facing the same situation https://codesandbox.io/s/0qx6lq4lrn?fontsize=14.

Basically I hoped to run "startup" code, setting up API, managing saved session, redirecting to sign-in if user is not authenticated, connecting store to network events etc., basically setting up App state. First expectation for that is to put useEffect at the root level, ie. in App:

import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import { navigate } from "hookrouter";

import "./styles.css";

function App() {
  let auth = useStore('auth')
  useEffect(() => {
    console.log("startup");
    if (!auth.tokens) navigate('/sign-in')
    // ...restore saved session, redirects etc.
  }, []);

  return <Content />;
}

function Content() {
  useEffect(() => {
    console.log("init data");
    // ...fetch content etc.
  }, []);

  return <></>;
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

But the children effects run before the App.
What would be the right way to organize startup?

@gaearon @mpgon

All 11 comments

Why would the order matter in that case? First paint would happen for both at the same time.

In this simple example yes, but imagine wanting to measure this in an expensive tree, where you have intermediate components with expensive fetches, etc. For example, imagine you want to measure the difference between when they see the navbar and they see a post detail.

But that's not how React works. It won't somehow paint the child separate from the parent. Maybe you can add an example where it matters? I don't understand the pattern you're referring to.

If you mean that the child is mounted later — why does the order matter then?

I'm thinking of a situation where the child first render happens after the parent first render, but the effect of the child is fired first. To clarify:
t=0

<Parent useEffect("effect parent", [])> 
    <Spinner />
</Parent>

t=1

<Parent>
    <Child useEffect("effect child", []) />
</Parent>

and the "effect child" can be fired before "effect parent"

This one illustrates it better. https://codesandbox.io/s/j3mr80r9m9?fontsize=14 Although admittedly it doesn't seem to reproduce what I'm experiencing in my app. So the question is, is it impossible, in this codesandox example, for the CHILD first render to ever be flushed before the PARENT first render?

A child's first render can't be flushed before parent's first render by definition — a child is a part of the parent.

That's what I was trying to find out. Then the problem must lie elsewhere. Thank you for your time!

Can we please elaborate the answer? Facing the same situation https://codesandbox.io/s/0qx6lq4lrn?fontsize=14.

Basically I hoped to run "startup" code, setting up API, managing saved session, redirecting to sign-in if user is not authenticated, connecting store to network events etc., basically setting up App state. First expectation for that is to put useEffect at the root level, ie. in App:

import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import { navigate } from "hookrouter";

import "./styles.css";

function App() {
  let auth = useStore('auth')
  useEffect(() => {
    console.log("startup");
    if (!auth.tokens) navigate('/sign-in')
    // ...restore saved session, redirects etc.
  }, []);

  return <Content />;
}

function Content() {
  useEffect(() => {
    console.log("init data");
    // ...fetch content etc.
  }, []);

  return <></>;
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

But the children effects run before the App.
What would be the right way to organize startup?

@gaearon @mpgon

@dy I think what you want is to not render <Content /> until you've done whatever setup this is. So maybe have some state variable:

const [isSetupComplete, setIsSetupComplete] = useState(false);

// Do your useEffect work, when complete: setIsSetupComplete(true);

return (isSetupComplete) ? <Content /> : <></>;

https://codesandbox.io/s/rwyqq99nyn

Ok, what I needed is useAsync:


  // startup
  let [complete, error, loading] = useAsync(async () => {
    // auth
    if (auth) {
      setToken(auth)
    }

    if (isSignedIn === false) {
      navigate('/sign-in')
    }
    if (resetPasswordUserLogin) {
      navigate('/password/reset')
    }

    return true
  }, [])
Was this page helpful?
0 / 5 - 0 ratings