When Does a React Component Re‑Render and How to Optimize It?

This article explains what triggers a React component re‑render, distinguishes required from unnecessary renders, and presents practical techniques—such as narrowing render scope, using memo, useMemo, useCallback, and context selectors—to improve performance in complex applications.

Huolala Tech
Huolala Tech
Huolala Tech
When Does a React Component Re‑Render and How to Optimize It?

What Is a Component Re‑Render

In React, components render for two main reasons: the initial render and a state update in the component or one of its ancestors. The latter case is what we call a re‑render, typically occurring after user interaction or an asynchronous data fetch.

Re‑renders can be classified as required re‑render (state change that must update the UI) or unnecessary re‑render (a parent’s re‑render causing a child to re‑render without affecting UI consistency). Unnecessary re‑renders are not a problem by themselves, but frequent or heavy re‑renders can cause performance issues.

What Operations Trigger Component Re‑Render

Component State Updates

Updating a component’s state—whether via class state, hooks state, or custom hook dependencies—triggers a re‑render. State updates usually happen inside effects or callbacks.

function Component() {
  const [state, setState] = useState(1);
  useEffect(() => {
    // ...
    setState(2);
  }, [...]);
  // ...
}

Parent Component Re‑Render

A parent component’s re‑render causes its children to re‑render, regardless of whether the props have changed.

function Parent() {
  return <Child />;
}

Context Changes

Changing a value provided by a Context can cause every component that consumes that Context to re‑render.

const Context = createContext<[number, Dispatch<SetStateAction<number>>]>(null!);

function Component1() {
  const [state, setState] = useContext(Context);
  return <button onClick={() => setState(v => v + 1)}>click</button>;
}

function Component2() {
  const [state] = useContext(Context);
  return <div>123</div>;
}

function Provider({ children }: PropsWithChildren) {
  return <Context.Provider value={useState(1)}>{children}</Context.Provider>;
}

function App() {
  return (
    <Provider>
      <Component1 />
      <Component2 />
    </Provider>
  );
}

Misconception: Props Changes Trigger Component Re‑Render

It is the parent’s re‑render that causes a child to re‑render, not the change of props per se. Only when a child is wrapped with React.memo or extends PureComponent does a props change determine whether it re‑renders.

How to Optimize Performance Issues Caused by Re‑Render

Re‑renders are fast in most cases; premature optimization is unnecessary. When needed, the following strategies can help.

Narrow the Re‑Render Scope

Extract modal logic into its own component so that only the modal‑related component re‑renders when the modal is shown.

Before:

function Component() {
  const [visible, setVisible] = useState(false);
  return (
    <div>
      <button onClick={() => setVisible(true)}>open</button>
      {visible ? <Modal /> : null}
      <SlowComponent />
    </div>
  );
}

After:

function ButtonWithModal() {
  const [visible, setVisible] = useState(false);
  return (
    <>
      <button onClick={() => setVisible(true)}>open</button>
      {visible ? <Modal /> : null}
    </>
  );
}

function Component() {
  return (
    <div>
      <ButtonWithModal />
      <SlowComponent />
    </div>
  );
}

components as props

Pass components as props to limit re‑renders to a specific wrapper.

Before:

function Component() {
  const [val, setVal] = useState('');
  return (
    <div onClick={() => setVal('...')}>
      <SlowComponent1 />
      <div>{val}</div>
      <SlowComponent2 />
      <SlowComponent3 />
    </div>
  );
}

After:

function ComponentWithClick({ top, bottom, children }) {
  const [val, setVal] = useState('');
  return (
    <div onClick={() => setVal('...')}>
      {top}
      <div>{val}</div>
      {children}
      {bottom}
    </div>
  );
}

function Component() {
  return (
    <ComponentWithClick top={<SlowComponent1 />} bottom={<SlowComponent3 />}>
      <SlowComponent2 />
    </ComponentWithClick>
  );
}

Proper Use of memo, useMemo, useCallback, and PureComponent

Wrapping expensive components with memo or generating them with useMemo prevents unnecessary re‑renders.

Before:

function Component() {
  return <SlowComponent />;
}

After:

const SlowComponentMemo = memo(SlowComponent);
function Component() {
  return <SlowComponentMemo />;
}

// or using useMemo
function Component() {
  const slowComponentNode = useMemo(() => <SlowComponent />, []);
  return slowComponentNode;
}

When props are reference types, combine useMemo with memo for optimal results.

Before:

function Component() {
  return <SlowComponent value={{ a: 1 }} />;
}

After:

const SlowComponentMemo = memo(SlowComponent);
function Component() {
  const value = useMemo(() => ({ a: 1 }), []);
  return <SlowComponentMemo value={value} />;
}

How to Optimize Context‑Induced Re‑Render

Combine with useMemo

Memoize the value passed to Context.Provider so that it only changes when the underlying state changes.

function Component({ children }) {
  const [state, setState] = useState(1);
  const value = useMemo(() => ({ state, setState }), [state]);
  return <Context.Provider value={value}>{children}</Context.Provider>;
}

Split Data More Granularly

Provide separate contexts for each piece of data to limit re‑renders to components that actually use the changed piece.

function Component({ children }) {
  const [state, setState] = useState({ a: 1, b: 2 });
  return (
    <Context1.Provider value={state.a}>
      <Context2.Provider value={state.b}>
        <Context3.Provider value={setState}>
          {children}
        </Context3.Provider>
      </Context2.Provider>
    </Context1.Provider>
  );
}

Use Context Selector

Selectors (via higher‑order components or useSelector -like hooks) let components subscribe only to the slice of context they need, preventing unrelated updates from causing re‑renders.

function withContextSelector(Component, selector) {
  const ComponentMemo = React.memo(Component);
  return (props) => {
    const data = useContext(Context);
    const contextProps = selector(data);
    return <ComponentMemo {...props} {...contextProps} />;
  };
}

Libraries such as react-tracked implement this pattern using use-context-selector and proxies to automatically track used state.

Summary

The article first clarifies what a React component re‑render is, then analyzes common triggers, and finally summarizes practical optimization methods for re‑render‑related performance problems, offering a reference guide for developers facing performance bottlenecks. [1] connect(mapStateToProps?, mapDispatchToProps?): https://react-redux.js.org/api/connect [2] react‑redux useSelector: https://react-redux.js.org/api/hooks#useselector [3] use‑context‑selector: https://github.com/dai-shi/use-context-selector [4] react‑tracked: https://github.com/dai-shi/react-tracked

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.

performanceoptimizationReacthooksmemore-render
Huolala Tech
Written by

Huolala Tech

Technology reshapes logistics

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.