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.

Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Why Do Function Components Re‑Render? A Deep Dive into Hooks and Optimization

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

ReacthooksuseStateuseCallbackReact.memofunction componentsre-render
Tencent IMWeb Frontend Team
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.