Suggest an editImprove this articleRefine the answer for “What causes a re-render?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**A re-render** is a repeated call of the component function so that React understands what JSX it should display; it happens when `state` changes inside a component, when `props` from a parent change, when a context value changes, when a parent re-renders, or when an element's `key` changes. **Key point:** React compares the new value with the old one through `Object.is()`, and if the `state` has not actually changed, a re-render is not triggered.Shown above the full answer for quick recall.Answer (EN)Image## Key idea > **The Virtual DOM is the foundation** on which React implements a **component's lifecycle.** React does not work directly with the real DOM on every change. Instead: 1. It builds a **virtual tree (Virtual DOM)** - JS objects describing the interface structure. 2. When state changes (`state`, `props`), React creates a **new version of the virtual tree**. 3. It compares it with the old version (the **reconciliation** process). 4. It computes the **minimal set of changes** that need to be applied to the real DOM. 5. It applies them in the **commit phase**. And all **lifecycle** events happen *around* these actions. --- ## How the Virtual DOM relates to lifecycle phases Here is the relationship, step by step: | Stage | What React does with the Virtual DOM | Which methods / hooks are called | |---|---|---| | **Mounting** | React creates the **first virtual tree** from the JSX components | `useEffect` (after commit), `useLayoutEffect`, `componentDidMount` | | **Updating** | React creates a **new virtual tree**, compares it with the old one -> finds the differences (diffing) | `useEffect` (cleanup + new), `useLayoutEffect`, `componentDidUpdate` | | **Unmounting** | React removes the node from the virtual tree -> removes the corresponding DOM elements | cleanup functions in `useEffect`, `componentWillUnmount` | --- ## Example: step by step ```javascript function Counter() { const [count, setCount] = useState(0); useEffect(() => { console.log('effect'); return () => console.log('cleanup'); }); return <button onClick={() => setCount(c => c + 1)}>{count}</button>; } ``` 1. **Mounting** - React calls `Counter()` -> creates the Virtual DOM for `<button>0</button>`. - There is nothing to compare against yet -> creates the DOM element `<button>`. - After the commit it calls `useEffect()`. ```javascript render -> diff -> commit -> useEffect ``` 2. **Updating (**`setCount`**)** - React calls `Counter()` again -> new Virtual DOM: `<button>1</button>`. - Compares the old (`<button>0</button>`) with the new (`<button>1</button>`) -> sees a difference in the text. - Changes only the text in the DOM. - Calls the cleanup of the old effect -> then the new `useEffect`. ```javascript render -> diff -> minimal DOM update -> cleanup -> new useEffect ``` 3. **Unmounting** - React removes the `Counter` Virtual DOM node. - Removes the `<button>` element from the DOM. - Calls `cleanup()` from `useEffect`. ```javascript unmount -> cleanup -> remove DOM ``` --- ## Relation to the Render and Commit phases | Phase | Work with the Virtual DOM | Lifecycle | |---|---|---| | **Render phase** | React re-creates the Virtual DOM (in memory) | the component's `render()`, reading state, computing JSX | | **Commit phase** | React updates the real DOM based on the diff | `useLayoutEffect`, `useEffect`, `componentDidMount`, `componentDidUpdate`, cleanup | --- ## Key point > The Virtual DOM is a "workspace" where React decides **what needs to change**. > The lifecycle is a "timeline" of **when** those changes happen and **when you can hook into** that process. --- ## Visually ```javascript (state changed) ↓ ┌──────────────┐ │ Render phase │ ← build a new Virtual DOM └──────────────┘ ↓ compare with the previous one (diffing) ↓ ┌──────────────┐ │ Commit phase │ ← update the real DOM └──────────────┘ ↓ call useEffect() ``` --- Would you like me to draw a **diagram of a component's lifecycle** (mounting -> updating -> unmounting) showing where React works with the Virtual DOM and where the hooks (`useEffect`, `useLayoutEffect`, `cleanup`) are called? ##### You said: What causes a re-render? ###### ChatGPT said: Great question - understanding **exactly what causes a re-render** is the key to **optimizing React applications**. Let's break it all down in detail, without "magic": when React decides to call a component again and why. --- ## What is a "render" in general? > A "render" is **a call of the component's function** so React understands *what JSX* (that is, the Virtual DOM) it should display. > This **does not always mean a change to the real DOM!** React can call a component again, compare the result with the previous Virtual DOM, and... **change nothing** in the browser if everything is the same. --- ## A re-render happens when React decides that it **needs to recompute the JSX**. Here are **all the reasons** this happens --- ## 1. A change to `state` inside the component The most obvious one: ```javascript function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>{count}</button>; } ``` - Every call to `setCount()` -> React calls `Counter()` again. - The component **re-renders**. - Then React compares the old and new Virtual DOM (diffing) -> and changes only the changed text in the real DOM. > Even if `setState` sets the same value (`setCount(0)` when `count === 0`), React compares the old and new value and **does not trigger a re-render** if they are equal (`Object.is()` comparison). --- ## 2. A change to `props` coming from the parent If the parent re-rendered and passed **new props** (by reference or by value), React will re-render the child too. ```javascript function Child({ value }) { console.log('render child'); return <div>{value}</div>; } function Parent() { const [count, setCount] = useState(0); return ( <> <button onClick={() => setCount(c => c + 1)}>+</button> <Child value={count} /> {/* -> re-renders when count changes */} </> ); } ``` - `Parent` changed -> React calls `Parent()`. - The new Virtual DOM includes `<Child value={newCount}>`. - `Child`'s prop changed -> it **re-renders too**. > Even if the parent changed an **unused** piece of state, the component function is still called, but React can **optimize** this with `React.memo`. --- ## 3. A change in context (`useContext`) If you use context and its value has changed, **all the components** that read it through `useContext()` **re-render**: ```javascript const ThemeContext = createContext('light'); function App() { const [theme, setTheme] = useState('light'); return ( <ThemeContext.Provider value={theme}> <Toolbar /> </ThemeContext.Provider> ); } function Toolbar() { const theme = useContext(ThemeContext); // on change - re-render return <div>{theme}</div>; } ``` > To keep all context consumers from re-rendering, use **selectors** or split the contexts. --- ## 4. A change in the parent If the parent **re-renders**, then React **by default re-renders all of its children**, because it calls the parent's JSX again: ```javascript function Parent() { const [count, setCount] = useState(0); return ( <> <Child /> {/* will be called again */} <button onClick={() => setCount(c => c + 1)}>+</button> </> ); } ``` Even if `Child` does not depend on `count`, React will still **recreate its JSX**. To avoid this, use `React.memo(Child)`. --- ## 5. A change to a component's `key` If a component's `key` changes, React treats it as a **new element** -> unmounts the old one, mounts the new one. ```javascript {list.map(item => ( <Row key={item.id} data={item} /> ))} ``` If `id` changes -> `Row` **is created anew** (not updated). --- ## 6. A change to the parent's `Context.Provider`, even without a change in value React calls all consumers again if a **new value object** is created on every render: ```javascript <ThemeContext.Provider value={{ color: 'black' }}> ``` Here a new object is created on every call -> `useContext` sees a new value -> a re-render. To avoid this, memoize the value: ```javascript <ThemeContext.Provider value={useMemo(() => ({ color: 'black' }), [])}> ``` --- ## 7. An update through `forceUpdate` (rare, but possible) In class components, `this.forceUpdate()` explicitly forces React to call `render()` again, even if `state` and `props` have not changed. --- ## Key table | Reason | What changed | Does it cause a re-render? | |---|---|---| | `setState()` inside the component | Yes | Yes | | New `props` from the parent | Yes | Yes | | The parent re-rendered | Even without a change in props | Yes | | A context value (`useContext`) | Yes | Yes | | A new object/function in props (by reference) | Yes, if the reference changed | Yes | | A new `key` on the element | Yes | Yes (remounting) | | `setState()` with the same value | No (React compares with `Object.is()`) | No | | `React.memo()` + identical props | No changes | No | --- ## How to avoid unnecessary re-renders 1. `React.memo(Component)` - memoization by props. 2. `useMemo()` - memoization of computations inside the component. 3. `useCallback()` - memoization of functions passed to children. 4. `useContextSelector()` or splitting contexts. 5. Do not create new objects/functions directly in JSX.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.