Closed mpgon closed 5 years ago
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
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
}, [])
As far as I can tell child effects run in order, first in first out, all else being equal. I created a tiny component to capitalise on this behaviour and run "parent" effects first:
export function Effect({ effect }) {
useEffect(() => effect?.(), [effect]);
return null;
}
See https://gist.github.com/nikparo/33544fe0228dd5aa6f0de8d03e96c378 for more details.
I'm facing an issue with this now. I'm abstracting the updating of page title in a component that wraps children with Route
component from react-router-dom. When I create nested routes with that, the title of the children components updates before its parent, therefore the page title is overridden by its parent page title.
This is an arbitrary example of that
import { useEffect } from 'react';
import { Redirect, Route } from 'react-router-dom';
export function CustomRoute({
children,
title,
...props
}) {
useEffect(() => {
if (title) {
document.title = title;
}
}, [title]);
return <Route {...props}>{children}</Route>;
}
function AppRouter() {
return (
<Switch>
<CustomRoute title="Profile" path="/profile">
<ParnetComponent />
</CustomRoute>
</Switch>
)
}
function Profile() {
return (
<Switch>
<CustomRoute title="Create - Profile" path="/profile/create">
<CreateProfile />
</CustomRoute>
</Switch>
)
}
function CreateProfile() {
return (
<Switch>
<CustomRoute path="/profile/create/personal-info" title="Personal Info - Create - Profile">
<PersonalInfo />
</CustomRoute>
<CustomRoute path="/profile/create/history" title="Historty Info - Create - Profile">
<History />
</CustomRoute>
<CustomRoute path="/profile/create/completed" title="Completed - Create - Profile">
<Completed />
</CustomRoute>
<Route
exact
path="/"
render={() => (
<Redirect
to={{
pathname: '/profile/create/personal-info',
}}
/>
)}
/>
</Switch>
)
}
For this use case the useEffect order matters to me.
@abetss You could use my idea above to get around that. In short, you would end up with something like this:
function DocTitle({ title }) {
useEffect(() => {
if (title) {
document.title = title;
}
}, [title]);
return null;
}
export function CustomRoute({
children,
title,
...props
}) {
// Child effects are run in order. Therefore the title is updated before any other effects are called.
return (
<Route {...props}>
<DocTitle title={title} />
{children}
</Route>
);
}
Edit: Moved <DocTitle />
to be within <Route />
.
Hi @abetss,
Your probably already found a satisfactory solution, but here is another: https://gist.github.com/knyzorg/a4395fcc4e8d53d5be4dff4ef5228379
It's more intricate, but it comes with unit tests.
Well, i am currently facing this issue right now. I am abstracting that when I reload a page then a page containing child component will request an API containing value from the parent component. But the child component always fired first before the parent component. So that is my solution to fix it, you just use useLayoutEffect on the parent component instead of using useEffect. That's my example code, hope it will help you and someone else.
import React, { useEffect, useLayoutEffect } from "react";
import ReactDOM from "react-dom";
function Parent({ children }) {
useLayoutEffect(() => {
console.log("Parent first render");
});
return (
<div>
<h2>parent</h2>
{children}
</div>
);
}
function Child() {
useEffect(() => {
console.log("Child will render after");
});
return <h3>child</h3>;
}
function App() {
return (
<Parent>
<Child />
</Parent>
);
}
}
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.
How could I accomplish this with hooks?