Detecting and Optimizing Repeated Rendering in Large React Applications with React Scan

The article explains how repeated rendering slows large React applications, introduces the React Scan tool for automatically detecting unnecessary renders, details its installation via script tag or npm, describes its core APIs, and shows how to combine it with memoization, useCallback, useMemo, and other optimization techniques to improve performance.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Detecting and Optimizing Repeated Rendering in Large React Applications with React Scan

When building large React applications, performance problems often arise due to repeated rendering. Repeated rendering is a major bottleneck, especially before the React Compiler became widely adopted. React Scan is an automatic detection tool that highlights rendering issues, allowing developers to precisely locate components that need fixing.

Learning Objectives

Repeated Rendering Issues : Understand the various cases of repeated rendering in React and their impact on performance.

React Scan Tool : Master the installation and usage of React Scan, both via a script tag and npm.

API Usage : Familiarize with the main APIs such as scan, withScan, getReport and setOptions.

Performance Optimization Techniques : Learn to use React.memo, useCallback, useMemo, shouldComponentUpdate and PureComponent to improve React app performance.

Practical Application : Apply the tool in a real project with example code.

React Repeated Rendering

React monitors component state and props to decide when a component should re‑render. Changes trigger re‑rendering of the component and its children, which can lead to unnecessary work.

1. Reference Types Trigger Re‑render

React performs a shallow comparison on objects and arrays. Even if the content is unchanged, a new reference forces a re‑render.

Example:

// Example causing `ExpensiveComponent` to re‑render frequently
<ExpensiveComponent onClick={() => alert('hi')} style={{ color: 'purple' }} />

In the code above, onClick and style are recreated on each render, causing ExpensiveComponent to re‑render even when its visual output does not change.

2. Unnecessary Parent‑to‑Child Updates

A parent’s state change can cause child components to re‑render even if the child does not depend on that state.

Example:

function ParentComponent() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increase Count</button>
      <ChildComponent />
    </div>
  );
}

function ChildComponent() {
  // Child does not use `count` but still re‑renders each time `count` changes
  return <div>Child Component</div>;
}

3. Frequent Internal State Changes

Components that update internal state very often cause their own frequent re‑renders.

Example:

function Counter() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const interval = setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);
    return () => clearInterval(interval);
  }, []);
  return <div>Count: {count}</div>;
}

Installation

You can install React Scan in two ways:

Via Script Tag

Insert the following script before any other scripts:

<!doctype html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/react-scan/dist/auto.global.js"></script>
    <!-- rest of your scripts go under -->
  </head>
  <body>
    <!-- ... -->
  </body>
</html>

For Next.js, add the script to pages/_document.tsx:

import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
  return (
    <Html lang="en">
      <Head>
        <script src="https://unpkg.com/react-scan/dist/auto.global.js" />
        {/* other scripts */}
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

Via npm

If you prefer package management, run:

npm install react-scan

Then import before React:

import { scan } from 'react-scan'; // import before React
import React from 'react';

scan({
  enabled: true,
  log: true,
  playSound: true,
  showToolbar: true,
  report: true,
});

API Reference

scan(options)

Automatically scans the application for renders.

scan({
  enabled: true,               // enable/disable scanning
  includeChildren: true,       // include children of components wrapped with withScan
  playSound: true,              // toggle sound effect
  log: false,                  // log render info to console
  showToolbar: true,           // display toolbar UI
  renderCountThreshold: 0,     // only show components rendered more than this count
  report: false,               // report data to getReport()
  onCommitStart: () => {},
  onRender: (fiber, render) => {},
  onCommitFinish: () => {},
  onPaintStart: (outline) => {},
  onPaintFinish: (outline) => {},
});

withScan(Component, options)

Scans a specific component.

function Component(props) {
  // ...
}

withScan(Component, {
  enabled: true,
  includeChildren: true,
  playSound: true,
  log: false,
  showToolbar: true,
  renderCountThreshold: 0,
  report: false,
  onCommitStart: () => {},
  onRender: (fiber, render) => {},
  onCommitFinish: () => {},
  onPaintStart: (outline) => {},
  onPaintFinish: (outline) => {},
});

getReport()

Retrieves a summary report of all components and their render statistics.

scan({ report: true });
const report = getReport();
for (const component in report) {
  const { count, time } = report[component];
  console.log(`${component} rendered ${count} times, took ${time}ms`);
}

setOptions(options) & getOptions()

Adjust or retrieve the current scanning configuration.

setOptions({ enabled: true, includeChildren: true, playSound: true });
const options = getOptions();
console.log(options);

Demo

A complete demo that integrates React Scan with several components:

import React, { useState } from 'react';
import type { FC } from 'react';
import { scan, getReport } from 'react-scan';
import ExpensiveComponent from './components/ExpensiveComponent';
import Counter from './components/Counter';
import ParentComponent from './components/ParentComponent';

// Initialize React Scan
scan({
  enabled: true,
  log: true,
  playSound: true,
  showToolbar: true,
  report: true,
});

setInterval(() => {
  const report = getReport();
  for (const component in report) {
    const { count, time } = report[component];
    console.log(`${component} rendered ${count} times, took ${time}ms`);
  }
}, 1000);

const App: FC = () => {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');
  return (
    <div className="App" style={{ padding: '20px' }}>
      <h1>React Scan Demo</h1>
      <button
        onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
        style={{ marginBottom: '20px' }}
      >
        切换主题
      </button>
      <div
        style={{
          display: 'grid',
          gap: '20px',
          backgroundColor: theme === 'light' ? '#fff' : '#333',
          color: theme === 'light' ? '#000' : '#fff',
          padding: '20px',
        }}
      >
        <ExpensiveComponent onClick={() => alert('clicked')} style={{ color: 'purple' }} />
        <Counter />
        <ParentComponent />
      </div>
    </div>
  );
};

export default App;

Switching the theme lets you see which child components re‑render and view a full performance report.

FAQ

Why choose this over React DevTools? React DevTools does not clearly distinguish necessary from unnecessary renders and lacks a programmable API. React Scan provides explicit render counts and a toolbar.

Does it support React Native? Support is coming soon.

Is there a Chrome extension? It will be released shortly.

General React Performance Solutions

1. Use React.memo

React.memo

is a higher‑order component that caches the rendered output of a functional component when its props have not changed.

const ChildComponent = React.memo(function ChildComponent() {
  return <div>Child Component</div>;
});

2. Use useCallback and useMemo

These hooks cache functions and computed values to avoid recreating them on each render.

function ParentComponent() {
  const [count, setCount] = useState(0);
  const handleClick = useCallback(() => {
    alert('hi');
  }, []);
  const style = useMemo(() => ({ color: 'purple' }), []);
  return <ExpensiveComponent onClick={handleClick} style={style} />;
}

3. Use shouldComponentUpdate or PureComponent

For class components, shouldComponentUpdate lets you control update logic, while React.PureComponent implements a shallow prop and state comparison automatically.

class ChildComponent extends React.PureComponent {
  render() {
    return <div>Child Component</div>;
  }
}
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.

JavaScriptReactReact Scan
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

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.