Why Do Function Components Re‑Render? A Deep Dive into Hooks and Optimization
This article explains the three main reasons function components re‑render—useState/useReducer updates, parent updates, and context changes—and shows how to control unnecessary renders with techniques such as memoization, useCallback, useRef, and component extraction.
Function component re‑render can be triggered by three main situations: updates via useState / useReducer, parent component updates, and context changes.
1. Updates caused by useState or useReducer
1.1 Regular usage
Example Counter component that logs "counter render" on each click.
const Counter = () => {
console.log('counter render');
const [count, addCount] = useState(0);
return (
<div className="counter">
<div className="counter-num">{count}</div>
<button onClick={() => {addCount(count + 1)}}>add</button>
</div>
);
}1.2 Immutable state
When state is a reference type, React compares old and new state with Object.is; if they are equal, no re‑render occurs.
const Counter = () => {
console.log('counter render');
const [count, addCount] = useState({ num: 0, time: Date.now() });
const clickHandler = () => {
count.num++;
count.time = Date.now();
addCount(count);
};
return (
<div className="counter">
<div className="counter-num">{count.num}, {count.time}</div>
<button onClick={clickHandler}>add</button>
</div>
);
}Therefore state must be immutable and a new value must be returned for an update to be effective.
1.3 Forced update
Function components lack forceUpdate, but you can simulate it by updating a dummy state created with useState({}).
const [, forceUpdate] = useState({});
forceUpdate({});2. Parent component updates
2.1 Regular usage
Adding a child component Hello causes it to re‑render on every parent state change, even though its props do not change.
const Hello = ({ name }) => {
console.log('hello render');
return <div>hello {name}</div>;
};
const App = () => {
console.log('app render');
const [count, addCount] = useState(0);
return (
<div className="app">
<Hello name="react" />
<div className="counter-num">{count}</div>
<button onClick={() => {addCount(count + 1)}}>add</button>
</div>
);
};2.2 Optimizing component design
2.2.1 Extract updating part into a separate component
Move the counter logic into its own Counter component so Hello is not affected.
const App = () => {
console.log('app render');
return (
<div className="app">
<Hello name="react" />
<Counter />
</div>
);
};2.2.2 Use children slots for non‑changing parts
const App = ({ children }) => {
console.log('app render');
const [count, addCount] = useState(0);
return (
<div className="app">
{children}
<div className="counter-num">{count}</div>
<button onClick={() => {addCount(count + 1)}}>add</button>
</div>
);
};
// Usage
<App>
<Hello name="react" />
</App>2.3 React.memo
Class components have PureComponent and shouldComponentUpdate. Function components can use React.memo to skip re‑render when props are shallowly equal.
const Hello = React.memo(({ name }) => {
console.log('hello render');
return <div>hello {name}</div>;
});
const App = () => {
console.log('app render');
const [count, addCount] = useState(0);
return (
<div className="app">
<Hello name="react" />
<div className="counter-num">{count}</div>
<button onClick={() => {addCount(count + 1)}}>add</button>
</div>
);
};Memo uses shallowEqual by default; if a prop is a newly created function each render, memo will still re‑render.
2.3.1 useCallback
Wrap callbacks with useCallback to keep the same reference, preventing unnecessary re‑renders.
const clickHandler = useCallback(() => {
console.log('hello click');
}, []);If the callback uses a state value, the dependency array must include that state; otherwise the callback captures a stale value.
2.3.2 useRef & useEffect
Store mutable values in a ref and update them in useEffect; callbacks can read ref.current to get the latest state without changing their identity.
const App = ({ children }) => {
console.log('counter render');
const [count, addCount] = useState(0);
const countRef = useRef(count);
const clickHandler = useCallback(() => {
console.log('count: ', countRef.current);
}, [countRef]);
useEffect(() => {
countRef.current = count;
}, [count]);
return (
<div className="counter">
<Hello name="react" onClick={clickHandler} />
<div className="counter-num">{count}</div>
<button onClick={() => {addCount(count + 1)}}>add</button>
</div>
);
};Summary of the approach: use useRef to hold changing values, useEffect to sync them, and useCallback to return a stable function.
3. Context updates
Context changes also trigger re‑render; libraries like react‑redux and react‑router rely on this mechanism. For a deeper look, see the referenced article on React Context source analysis.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tencent IMWeb Frontend Team
IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
