Frontend Development 16 min read

Boost React Performance: Master Functional Components, Hooks, and Memoization

This article explains how functional components, React hooks, and memoization techniques such as React.memo, useCallback, and useMemo can be combined to reduce unnecessary re‑renders, simplify side‑effect management, and improve overall UI performance in modern React applications.

Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Boost React Performance: Master Functional Components, Hooks, and Memoization

1. Introduction

Functional components provide a concise, data‑driven way to build UI. By splitting a React component into three parts—data, computation, and rendering—we can identify several performance‑optimization directions.

Data: use caching to reduce the number of rerender calls.

Computation: precisely determine when and how much to update, reducing computation load.

Render: fine‑grained rendering lowers component complexity.

Today we mainly share data‑layer performance‑optimization techniques.

1.1 What can Hooks do that class components cannot?

While learning the React

hook api

, I discovered that hooks are more abstract and flexible than class lifecycle methods. The React FAQ asks a fascinating question— What can Hooks do that class components cannot?

Recently I found an answer while developing a feature that required passing a parameter via URL or cache to a newly opened tab. When the tab page loads, it reads the parameter and uses it to request additional data for rendering.

Initially I implemented the feature with a class component, but the code was hard to understand and maintain. Refactoring to a functional component made the code much cleaner.

1.2 A bad example (getDerivedStateFromProps + componentDidUpdate)

First I tried to use

getDerivedStateFromProps

and

componentDidUpdate

to compare props and control requests and updates.

We have a parameter called

productId

(also known as 商品id) that comes from the URL or cache. When the parent component receives an updated productId, it passes it to the child component via props.

<code>function Parent() {
  /**
   * When productId updates, pass it to Child
   */
  return &lt;Child productId={productId} /&gt;
}
</code>

When the parent changes the

productId

prop, the child uses

getDerivedStateFromProps

to detect the change, updates its state, and triggers a re‑render.

<code>static getDerivedStateFromProps(nextProps, prevState) {
  const { productId } = nextProps;
  if (productId !== prevState.productId) {
    return { productId };
  }
  return null;
}
</code>

After the state update,

componentDidUpdate

sends another request to fetch the product details.

<code>componentDidUpdate(prevProps, prevState) {
  // When state changes, request new data
  if (prevState.productId !== this.state.productId) {
    this.getProductDetailRequest();
  }
}

getProductDetailRequest = async () => {
  const { productId } = this.state;
  const { result } = await getProductDetail({ f_id: +productId });
  this.setState({ productDetail: result });
};
</code>

This implementation has several drawbacks:

Calling setState inside componentDidUpdate without proper guards can cause infinite loops.

State comparison is scattered across two lifecycle methods, making maintenance harder.

The demo code is overly complex for a simple use case.

1.3 Another bad example (componentWillReceiveProps)

Because

getDerivedStateFromProps

is static and cannot access

this

, side‑effects such as async requests cannot be placed there. Therefore we might fall back to

componentWillReceiveProps

to trigger a request when new props arrive.

<code>componentWillReceiveProps(props) {
  const { productId } = props;
  if (`${productId}` === 'null') return; // no request
  if (productId !== this.state.productId) {
    this.getProductDetailRequest(productId);
  }
}
</code>

This approach works only for React versions prior to 16.4.

1.4 Managing side‑effects in class components

Two common solutions exist, each with drawbacks:

componentWillReceiveProps was marked unsafe in React 16.4 because it is easy to misuse and introduce side‑effects.

Combining getDerivedStateFromProps with componentDidUpdate adds complexity without real benefit.

Since React 16.8, functional components with hooks provide a cleaner way to handle side‑effects.

2. Functional Component Performance Optimizations

2.1 Pure Components

Pure components stem from the concept of pure functions: the output depends only on the input and is deterministic. In React, a component that renders the same UI for identical props and state can be treated as pure.

2.1.1 Shallow Comparison

Shallow comparison checks primitive values for equality and reference values for identical memory addresses. It runs before the diff algorithm, preventing unnecessary re‑renders with less overhead.

2.1.2 Pure Component APIs

Pure components perform a shallow comparison of props and state, similar to implementing

shouldComponentUpdate

manually.

Class components can extend

PureComponent

; functional components can be wrapped with

React.memo

.

<code>import React, { PureComponent } from 'react';

class App extends PureComponent {}
export default App;
</code>

If deep changes occur in referenced data, a pure component will not update, so custom

shouldComponentUpdate

may be needed.

2.1.3 React.memo

React.memo

is a higher‑order component that memoizes the result of a functional component based on shallow prop comparison.

<code>function MyComponent(props) {
  /* render using props */
}
function areEqual(prevProps, nextProps) {
  // custom comparison logic
}
export default React.memo(MyComponent, areEqual);
</code>

When a component uses

useState

,

useReducer

, or

useContext

, changes in state or context will still cause a re‑render even if props are unchanged.

2.1.4 Summary

Converting class components to functional components and applying

React.memo

is the most convenient way to create pure components.

2.2 useCallback

Passing callback functions via props can cause child components to re‑render on every parent render. Wrapping the callback with

useCallback

memoizes it, updating only when its dependencies change.

<code>const Parent = () => {
  const [title, setTitle] = useState('Title');
  const callback = () => {
    setTitle('Changed Title');
  };
  const memoizedCallback = useCallback(callback, []);
  return (<>
    <h1>{title}</h1>
    <Child onClick={memoizedCallback} />
  </>);
};

const Child = ({ onClick }) => (
  <button onClick={onClick}>change title</button>
);
</code>
useCallback(fn, deps)

is equivalent to

useMemo(() => fn, deps)

.

2.3 useMemo

useMemo

caches the result of an expensive calculation and recomputes only when its dependencies change.

<code>function computeExpensiveValue() {
  // long calculation
  return xxx;
}
const memoizedValue = useMemo(computeExpensiveValue, [a, b]);
</code>

Example with factorial calculation:

<code>export function CalculateFactorial() {
  const [number, setNumber] = useState(1);
  const [inc, setInc] = useState(0);

  const factorial = useMemo(() => factorialOf(number), [number]);

  const onChange = event => setNumber(Number(event.target.value));
  const onClick = () => setInc(i => i + 1);

  return (
    <div>
      Factorial of <input type="number" value={number} onChange={onChange} /> is {factorial}
      <button onClick={onClick}>Re‑render</button>
    </div>
  );
}

function factorialOf(n) {
  console.log('factorialOf(n) called!');
  return n <= 0 ? 1 : n * factorialOf(n - 1);
}
</code>

When

inc

changes, the component re‑renders, but because

number

is unchanged, the memoized factorial value is reused.

3. Conclusion

1. Combining functional components with the hook API offers a cleaner way to manage side‑effects, making it the preferred approach for the scenarios discussed in the introduction.

2. In plain terms,

React.memo

caches the virtual DOM,

useCallback

caches functions, and

useMemo

caches values.

Performance OptimizationReactHooksMemoizationFunctional Components
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

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.