Suggest an editImprove this articleRefine the answer for “component rendering order and hook calling in React”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Component rendering order in React** - React traverses the component tree depth-first: parent renders first, then children recursively, then siblings. ```jsx // App -> Header -> Main -> Sidebar -> Content -> Footer const [count, setCount] = useState(0); // Hook 1 - always position 0 const ref = useRef(null); // Hook 2 - always position 1 ``` **Key point:** React identifies hooks by call index in a per-fiber linked list, not by name - so hook order must never change between renders.Shown above the full answer for quick recall.Answer (EN)Image**Component rendering order in React** - React traverses the component tree depth-first on every update, calling each component function top-down, while hooks inside each component must fire in the exact same sequence every single render. ## Theory ### TL;DR - Rendering is like a family dinner: parent sits first, then calls each child to the table one by one before moving to the next guest at the table - React renders parent, walks into children depth-first, then moves to siblings - Hooks are identified by call position in a per-component linked list, not by name - If hook count changes between renders, React throws an error - Condition logic belongs inside hooks, never around them ### Quick example ```jsx function Parent() { console.log('Parent render'); // 1st return <Child />; } function Child() { console.log('Child render'); // 2nd const [count, setCount] = useState(0); // Hook 1 - always position 0 const ref = useRef(null); // Hook 2 - always position 1 useEffect(() => { console.log('Child effect'); // 4th - after browser paint }, []); console.log('Child hooks done'); // 3rd return <div ref={ref}>{count}</div>; } // Console output: // Parent render // Child render // Child hooks done // Child effect ``` Parent renders, then Child renders synchronously, then effects fire after the browser paints. That sequence is fixed. ### Rendering order: depth-first traversal React walks the JSX tree top-down. Given this structure: ```jsx function App() { return ( <> <SiblingA /> <Parent /> <SiblingB /> </> ); } function Parent() { return <><ChildA /><ChildB /></>; } function ChildA() { console.log('ChildA'); return null; } function ChildB() { console.log('ChildB'); return null; } function SiblingA() { console.log('SiblingA'); return null; } function SiblingB() { console.log('SiblingB'); return null; } // Output: App -> SiblingA -> Parent -> ChildA -> ChildB -> SiblingB ``` React goes deep into each subtree before moving to the next sibling. `SiblingB` does not start rendering until the entire `Parent` subtree is done. This matters when debugging. If `ChildA` is slow, it blocks `ChildB` and `SiblingB`. Adding `console.log` at the top of each component function shows exactly this traversal. In practice, this single technique catches more unexpected re-render bugs than most profiling sessions. ### How React tracks hook state Every component in the Fiber architecture has its own linked list of hook states. Three hooks in a component means React stores them at positions 0, 1, and 2 in that list. On re-render, React replays those calls in the same order and reads position 0, 1, 2 to get the previous values. No names. No magic. Just index position. This is why hook order cannot change. Skip hook 1 on one render, and hook 2 now sits at position 0. React reads the wrong state. The bug is often silent until something breaks. ### Hook execution phases Hooks split into two groups based on when they fire. **During render:** `useState`, `useReducer`, `useContext`, `useRef`, `useMemo`, `useCallback`. These run synchronously as the component function executes, in declaration order. **After render:** `useLayoutEffect` fires after the DOM is updated but before the browser paints. Use it when you need to measure a DOM element or fix layout before the user sees the screen. `useEffect` fires after the browser finishes painting. That is the right place for data fetching, subscriptions, and timers. Cleanup runs in reverse order on unmount: children clean up before parents. ### When to apply this knowledge - **Debugging re-renders:** log component names at the top of each function, check which ones fire when they should not - **Custom hooks:** always call them at the top level, never inside loops or conditions - a custom hook is a function that calls other hooks, the same rules apply - **Performance:** wrap subtrees in `React.memo` to stop parent re-renders from cascading into children that do not depend on the changed state - **`useLayoutEffect` vs `useEffect`:** if you see a visual flicker when updating element size or position, switch from `useEffect` to `useLayoutEffect` - it prevents the browser from painting the intermediate state ### Common mistakes **Conditional hook call:** ```jsx // Wrong function Bad({ show }) { if (show) { const [count, setCount] = useState(0); // skipped when show is false } return <div />; } // Right function Good({ show }) { const [count, setCount] = useState(0); // always called return show ? <div>{count}</div> : null; } ``` When `show` flips from `true` to `false`, the hook at position 0 disappears. React reads the next hook's value into the wrong slot. You get wrong state or a crash with "Rendered fewer hooks than expected". **Hook inside a loop:** ```jsx // Wrong function BadList({ items }) { items.forEach(item => { const [state, setState] = useState(0); // count shifts with items.length }); } // Right function GoodList({ items }) { const [states, setStates] = useState(() => items.map(() => 0)); // one useState holds the whole array } ``` React throws "Rendered more hooks than during the previous render" when `items.length` changes. One `useState` holding an array solves it. **Early return before hooks:** ```jsx // Wrong function Bad({ user }) { if (!user) return null; // hooks below never called on this render const [active, setActive] = useState(false); return <div>{active}</div>; } // Right function Good({ user }) { const [active, setActive] = useState(false); // always runs first if (!user) return null; return <div>{active}</div>; } ``` **Condition inside a custom hook:** ```jsx // Wrong function useData(id) { if (!id) return null; const [data, setData] = useState(null); // conditional call inside hook } // Right function useData(id) { const [data, setData] = useState(null); useEffect(() => { if (id) fetchData(id).then(setData); }, [id]); return data; } ``` ### Real-world usage - **Next.js:** server-side rendering and hydration depend on matching render order between server and client. A hook order mismatch causes hydration errors that are hard to trace - **TanStack Query:** `useQuery` must be called at the top level - its cache key depends on stable call position - **Redux Toolkit:** `useSelector` in connected components follows the same linked list mechanism - **React DevTools Profiler:** shows render order and timing for every component in the tree - **Concurrent React 18:** render phase stays depth-first and synchronous; the commit phase can yield for priority work, but component and hook order within a single render pass is identical to previous React versions ### Follow-up questions **Q:** What is the render order for `<Parent><A/><B/></Parent><Sibling/>`? **A:** `Parent -> A -> B -> Sibling`. React finishes the entire `Parent` subtree before moving to `Sibling`. **Q:** Why can't you call `useState` inside an `if` statement? **A:** React identifies hooks by their call index in a per-fiber linked list. Skipping a call shifts all subsequent indices, so React reads wrong values on the next render. **Q:** What is the cleanup order when components unmount? **A:** Reverse of mounting. Children clean up before parents. This mirrors the bottom-up order in which effects are committed. **Q:** How does `useLayoutEffect` differ from `useEffect` in timing? **A:** `useLayoutEffect` fires synchronously after the DOM mutation but before browser paint. `useEffect` fires after paint. Use `useLayoutEffect` when you need to read or write DOM measurements before the user sees the result. **Q:** In React 18 concurrent mode, can render order change? **A:** The depth-first traversal order stays the same. Concurrent mode can pause and restart render work for priority, but within a single render pass the sequence is identical to previous React versions. **Q (senior):** A component re-renders with a shorter list. Hooks are correctly declared outside the loop. But `useCallback` handlers still reference old item values. Why? **A:** Stale closures. `useCallback` captures variables from the render when the callback was last created. If the dependency array does not include the changed values, the callback holds a reference to the old state. Add those values to the dependency array, or store the latest value in a ref to avoid triggering extra re-renders. ## Examples ### Basic: render order across a component tree ```jsx function App() { console.log('App'); return ( <> <Header /> <Main /> <Footer /> </> ); } function Main() { console.log('Main'); return ( <> <Sidebar /> <Content /> </> ); } function Header() { console.log('Header'); return <header />; } function Sidebar() { console.log('Sidebar'); return <aside />; } function Content() { console.log('Content'); return <main />; } function Footer() { console.log('Footer'); return <footer />; } // Output: // App // Header // Main // Sidebar // Content // Footer ``` `Header` completes before `Main` starts. `Sidebar` and `Content` both finish before `Footer` begins. Depth-first, siblings after all children of a parent. ### Intermediate: hooks inside a real dashboard component ```jsx function UserDashboard({ userId }) { // All hooks declared unconditionally at the top const [user, setUser] = useState(null); // Hook 1: always position 0 const [stats, setStats] = useState({}); // Hook 2: always position 1 const containerRef = useRef(null); // Hook 3: always position 2 useEffect(() => { if (userId > 0) { fetchUser(userId).then(setUser); // condition goes inside, not around } }, [userId]); if (!userId) return null; // early return AFTER all hooks return ( <div ref={containerRef}> <UserProfile user={user} /> <StatsPanel data={stats} /> </div> ); } // Render order: UserDashboard -> UserProfile -> StatsPanel // Hook order per render: useState(null), useState({}), useRef, useEffect ``` All three hooks fire on every render regardless of `userId`. The condition moves inside `useEffect`. The early return sits after hooks are done. This is the pattern React expects. ### Advanced: diagnosing and fixing a hook order bug ```jsx // Bug: hook count changes when items.length changes function BadList({ items }) { const renderedItems = items.map(item => { const [selected, setSelected] = useState(false); // wrong: different count each render return ( <li key={item.id} onClick={() => setSelected(s => !s)}> {item.name} {selected ? '(selected)' : ''} </li> ); }); return <ul>{renderedItems}</ul>; } // React error: "Rendered more hooks than during the previous render." // Fix: one useReducer at the top level, stable hook count function GoodList({ items }) { const [selected, dispatch] = useReducer( (state, id) => ({ ...state, [id]: !state[id] }), {} ); return ( <ul> {items.map(item => ( <li key={item.id} onClick={() => dispatch(item.id)}> {item.name} {selected[item.id] ? '(selected)' : ''} </li> ))} </ul> ); } ``` Moving per-item state into a loop breaks the fixed-length rule. One `useReducer` at the top level handles all items, keeps hook count stable at 1 regardless of list size, and avoids the crash when items are added or removed.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.