Answering the question asked: As far as I'm aware, React doesn't guarantee the order of the calls to your component functions for the children, though it would be really surprising if they weren't in first-to-last order between siblings (so, reliably calling FirstComponent at least once before calling Child1 the first time, in that App). But although the calls to createElement will reliably be in that order, the calls to the component functions are done by React when and how it sees fit. It's hard to prove a negative, but I don't think it's documented that they'll be in any particular order.
But:
If we are sure that effect from FirstComponent always runs first, then it could be useful to perform some initialization work there, which we want to be available to all other useEffects in the app. We can't do this with normal parent/child effects, because you can see that parent effect ("Main App") runs after child effect ("Child 2".)
I wouldn't do that even if you find documentation saying the order is guaranteed. Crosstalk between sibling components is not a good idea. It means the components can't be used separately from each other, makes the components harder to test, and is unusual, making it surprising to people maintaining the codebase. You can do it anyway, of course, but as is often the case, lifting state up most likely applies ("state" in the general case, not just component state). Instead, do any one-time initialization you need to do in the parent (App) — not as an effect, but as component state in the parent, or instance state stored in a ref, etc., and pass it to the children via props, context, a Redux store, etc.
In the below, I'll pass things to the children via props, but that's just for the purposes of an example.
State
The usual place to store information used by child elements is in the parent's state. useState supports a callback function that will only be called during initialization. That's where to put this sort of thing unless you have a good reason not to. In the comments, you've suggested you have a good reason not to, but I'd be remiss if I didn't mention it in an answer meant for others in the future, not just for you now.
(This example and the second one below pass the information to the children via props, but again, it could be props, context, a Redux store, etc.)
Example:
const { useState, useEffect } = React; let Child2 = () => { return <div>Child 2</div>; }; let Child1 = ({ value, children }) => { return ( <div> <div>value = {value}</div> {children} </div> ); }; let FirstComponent = ({ value }) => { return <div>value = {value}</div>; }; function App() { const [value] = useState(() => { // Do one-time initialization here (pretend this is an // expensive operation). console.log("Doing initialization"); return Math.floor(Math.random() * 1000); }); useEffect(() => { return () => { // This is called on unmount, clean-up here if necessary console.log("Doing cleanup"); }; }, []); return ( <div> <FirstComponent value={value} /> <Child1 value={value}> <Child2 /> </Child1> </div> ); } const root = ReactDOM.createRoot(document.getElementById("root")); root.render(<App />);
<div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Technically, I suppose you could use it for doing something that didn't result in any value at all, but that would be odd semantically and I don't think I'd recommend it.
Non-State
If it's information that can't be stored in state for some reason, you can use a ref, either to store it (although then you might prefer state) or to just store a flag saying "I've done my initialization." One-time initialization of refs is perfectly normal. And if the initialization requires cleanup, you can do that in a useEffect cleanup callback. Here's an example (this example does end up storing something other than a flag on the ref, but it could just be a flag):
const { useRef, useEffect } = React; let Child2 = () => { return <div>Child 2</div>; }; let Child1 = ({ value, children }) => { return ( <div> <div>value = {value}</div> {children} </div> ); }; let FirstComponent = ({ value }) => { return <div>value = {value}</div>; }; function App() { // NOTE: This code isn't using state because the OP has a reason // for not using state, which happens sometimes. But without // such a reason, the normal thing to do here would be to use state, // perhaps `useState` with a callback function to do it once const instance = useRef(null); if (!instance.current) { // Do one-time initialization here, save the results // in `instance.current`: console.log("Doing initialization"); instance.current = { value: Math.floor(Math.random() * 1000), }; } const { value } = instance.current; useEffect(() => { return () => { // This is called on unmount, clean-up here if necessary console.log("Doing cleanup"); }; }, []); return ( <div> <FirstComponent value={value} /> <Child1 value={value}> <Child2 /> </Child1> </div> ); } const root = ReactDOM.createRoot(document.getElementById("root")); root.render(<App />);
<div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Your specific example use case
Re the specific use case you linked (note: the code from the question may not be correct; I'm not trying to correct it here, not least because I don't use axios):
I am using an axios interceptor to handle errors globally, but would like to set the state of my app from the interceptor.
axios.interceptors.response.use( error => { AppState.setError(error) } )
(And you've indicated that AppState, whatever it is, is only accessible within App.)
I'm not a fan of modifying the global axios instance, it's another crosstalky thing that affects all code using axios in the page/app, regardless of whether it's your code or code in a library that may use axios in a way where it's not appropriate for your app to show an error state that occurs.
I'd lean toward decoupling the interceptor from the App state update:
- Have an
axios wrapper module taht exports a custom axios instance - Put the interceptor on that instance
- Provide a means of subscribing to error events from that module
- Have
App subscribe to the error event from that to set its state (and unsubscribe on unmount)
That sounds complicated, but it's only about 30 lines of code even if you don't have a prebuilt event emitter class:
import globalAxios from "axios"; const errorListeners = new Set(); export function errorSubscribe(fn) { errorListeners.add(fn); } export function errorUnsubscribe(fn) { errorListeners.delete(fn); } function useErrorListener(fn) { const subscribed = useRef(false); if (!subscribed.current) { subscribed.current = true; errorSubscribe(fn); } useEffect(() => { return () => errorUnsubscribe(fn); }, []); } export const axios = globalAxios.create({/*...config...*/}); instance.interceptors.response.use(() => { (error) => { for (const listener of errorListeners) { try { listener(error); } catch {} } }; });
Then in App:
import { axios, useErrorListener } from "./axios-wrapper"; function App() { useErrorListener((error) => AppState.setError(error)); // ... }
In code that needs to use that instance of axios:
import { axios } from "./axios-wrapper"; // ...
That's a bit barebones (it assumes you'd never have dependencies on the error callback function, for instance), but you get the idea.
A(B(), C(D)). Order would beB, D, C, A.