Frontend Development 20 min read

PureComponent vs Hooks: Mastering React Re‑renders and Performance

This article explores how PureComponent and shouldComponentUpdate address unnecessary re‑renders in class components, compares them with functional components and hooks, and provides practical techniques—including React.memo, useCallback, setState updater functions, and refs—to optimize rendering performance in modern React applications.

KooFE Frontend Team
KooFE Frontend Team
KooFE Frontend Team
PureComponent vs Hooks: Mastering React Re‑renders and Performance

The article examines the evolution of React performance optimization, starting with class‑based PureComponent and

shouldComponentUpdate

, and then showing how equivalent behavior can be achieved with functional components, hooks, and memoization.

PureComponent, shouldComponentUpdate: what problems they solve

Unnecessary re‑renders often originate from a parent component updating its state, which forces child components to render again.

<code>const Child = () => <div>render something here</div>;

const Parent = () => {
  const [counter, setCounter] = useState(1);
  return (
    <>
      <button onClick={() => setCounter(counter + 1)}>Click me</button>
      {/* child will re‑render when "counter" changes */}
      <Child />
    </>
  );
};</code>

The same effect occurs with class components:

<code>class Child extends React.Component {
  render() {
    return <div>render something here</div>;
  }
}

class Parent extends React.Component {
  constructor() {
    super();
    this.state = { counter: 1 };
  }
  render() {
    return (
      <>
        <button onClick={() => this.setState({ counter: this.state.counter + 1 })}>Click me</button>
        {/* child will re‑render when state changes */}
        <Child />
      </>
    );
  }
};</code>

To stop these re‑renders, class components provide

shouldComponentUpdate

. Returning

false

prevents the component from updating:

<code>class Child extends React.Component {
  shouldComponentUpdate() {
    // now child component won't ever re‑render
    return false;
  }
  render() {
    return <div>render something here</div>;
  }
};</code>

If a component needs to react to specific prop changes,

shouldComponentUpdate

can compare

nextProps

(and optionally

nextState

) with the current values:

<code>class Child extends React.Component {
  shouldComponentUpdate(nextProps) {
    if (nextProps.someprop !== this.props.someprop) return true;
    return false;
  }
  render() {
    return <div>{this.props.someprop}</div>;
  }
};</code>
<code>shouldComponentUpdate(nextProps, nextState) {
  if (nextProps.someprop !== this.props.someprop) return true;
  if (nextState.somestate !== this.state.somestate) return true;
  return false;
}</code>

Manually writing deep comparisons is error‑prone, so React offers

React.PureComponent

which implements a shallow prop and state check automatically:

<code>class PureChild extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = { somestate: 'nothing' };
  }
  render() {
    return (
      <div>
        <button onClick={() => this.setState({ somestate: 'updated' })}>Click me</button>
        {this.state.somestate}
        {this.props.someprop}
      </div>
    );
  }
};</code>

Using

PureChild

inside a parent prevents re‑renders caused by the parent’s state changes:

<code>class Parent extends React.Component {
  constructor() {
    super();
    this.state = { counter: 1 };
  }
  render() {
    return (
      <>
        <button onClick={() => this.setState({ counter: this.state.counter + 1 })}>Click me</button>
        {/* child will NOT re‑render when state changes */}
        <PureChild someprop="something" />
      </>
    );
  }
};</code>

PureComponent/shouldComponentUpdate vs functional components & hooks

Function components cannot use

shouldComponentUpdate

or extend

PureComponent

, but

React.memo

provides the same shallow‑compare behavior.

<code>const Child = ({ someprop }) => {
  const [something, setSomething] = useState('nothing');
  return (
    <div>
      <button onClick={() => setSomething('updated')}>Click me</button>
      {something}
      {someprop}
    </div>
  );
};

export const PureChild = React.memo(Child);
</code>

When the parent’s state changes,

PureChild

does not re‑render, mirroring the class‑based

PureComponent

behavior:

<code>const Parent = () => {
  const [counter, setCounter] = useState(1);
  return (
    <>
      <button onClick={() => setCounter(counter + 1)}>Click me</button>
      {/* won’t re‑render because of counter change */}
      <PureChild someprop="123" />
    </>
  );
};</code>

When a prop is a function (e.g., an

onClick

handler), the function reference changes on every parent render, breaking memoization. A custom comparison function can exclude such props:

<code>const areEqual = (prevProps, nextProps) => prevProps.someprop === nextProps.someprop;
export const PureChild = React.memo(Child, areEqual);
</code>

In practice, developers often prefer

useCallback

to memoize callbacks, optionally adding state dependencies:

<code>const Parent = () => {
  const onChildClick = () => { /* do something */ };
  const onChildClickMemo = useCallback(onChildClick, []);
  return <PureChild someprop="something" onClick={onChildClickMemo} />;
};
</code>

If the callback needs to read the latest state, include that state in the dependency array:

<code>const Parent = () => {
  const [counter, setCounter] = useState(1);
  const onChildClick = () => {
    if (counter > 100) return;
    // do something
  };
  const onChildClickMemo = useCallback(onChildClick, [counter]);
  return <PureChild someprop="something" onClick={onChildClickMemo} />;
};
</code>

Alternatively, use the functional form of

setState

to avoid listing state in the dependency array:

<code>const onChildClick = () => {
  setCounter(counter => {
    if (counter > 100) return counter;
    return counter + 1;
  });
};
</code>

Another pattern stores mutable data in a

ref

, which does not trigger re‑renders when updated:

<code>const Parent = () => {
  const [counter, setCounter] = useState(1);
  const mirrorStateRef = useRef(null);
  useEffect(() => { mirrorStateRef.current = counter; }, [counter]);
  const onChildClick = () => {
    if (mirrorStateRef.current > 100) return;
    // do something
  };
  const onChildClickMemo = useCallback(onChildClick, []);
  return <PureChild someprop="something" onClick={onChildClickMemo} />;
};
</code>

For props that are arrays or objects, memoization (via

useMemo

or external libraries) is required to keep the reference stable across renders:

<code>const someArray = useMemo(() => ([1, 2, 3]), []);
<PureChild someArray={someArray} />;
</code>

Skipping state updates – a quirky behavior

React will still invoke a component’s render function once after a state update, even if the new state equals the previous one. This ensures that effects run safely, but subsequent identical updates are ignored, preventing unnecessary re‑renders.

<code>const Parent = () => {
  const [state, setState] = useState(0);
  console.log('Log parent re‑renders');
  return (
    <>
      <button onClick={() => setState(1)}>Click me</button>
    </>
  );
};
</code>

The first click changes state from 0 to 1 and logs; the second click attempts to set 1 → 1, logs again because React still renders once, but further identical updates produce no log.

Conclusion

PureComponent can be replaced by wrapping functional components with

React.memo

to achieve identical re‑render control.

shouldComponentUpdate

prop‑comparison logic is expressed in

React.memo

via an optional custom comparator.

Function components no longer need to worry about unnecessary state updates; React handles them efficiently.

When passing callbacks to memoized components, use

useCallback

, the functional updater form of

setState

, or

ref

to keep the reference stable.

Array and object props should be memoized (e.g., with

useMemo

) to avoid breaking memoization.

ReactHooksMemoizationuseCallbackPureComponent
KooFE Frontend Team
Written by

KooFE Frontend Team

Follow the latest frontend updates

0 followers
Reader feedback

How this landed with the community

login 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.