npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-perf-scan

v0.1.1

Published

Dev-only wasted-render visualizer and memoization advisor for React 18 & 19

Readme

react-perf-scan

Dev-only wasted-render visualizer and memoization advisor for React 18 & 19.

CI License: MIT React

react-perf-scan wraps your React tree in a Profiler, intercepts every commit via the React DevTools global hook, and highlights components that re-rendered without any prop or state change — wasted renders. For each offender it generates copy-ready React.memo, useCallback, and useMemo code snippets.

Zero production cost. In production builds the library is a no-op stub (index.prod.*) so it ships 0 bytes of overhead to your users.


Features

  • 🔴 Visual flash — outlines DOM nodes of wasting components in real time
  • 📊 Floating dashboard — lists every wasting component, wasted count, and prop/state diffs
  • 💡 Memo suggestions — auto-generated React.memo / useCallback / useMemo snippets with one-click copy
  • 🚫 Suspense-aware — never marks renders under an active Suspense fallback as wasted
  • ⚙️ Configurable — flash color, duration, position, component filter, render threshold
  • 🧹 Clean teardowndestroyPerfScan() removes all instrumentation with zero side effects

Installation

# npm
npm install react-perf-scan --save-dev

# pnpm
pnpm add react-perf-scan -D

# yarn
yarn add react-perf-scan --dev

Or clone and build locally (see Contributing).

Peer dependencies (already in your project):

react >= 18.0.0
react-dom >= 18.0.0

Quick Start

Call initPerfScan() before createRoot(...).render(...) so the library can wrap your tree with a React Profiler.

// src/main.tsx
import { initPerfScan } from 'react-perf-scan'
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { App } from './App'

// Only runs in development — no-op in production builds
initPerfScan()

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

That's it. Open your app in the browser and interact with it. Any component that re-renders without a prop or state change will flash red and appear in the dashboard.


Configuration

initPerfScan({
  enabled: true,              // false → disables all instrumentation
  flashColor: 'rgba(255, 0, 0, 0.3)', // CSS color for the wasted-render outline flash
  flashDuration: 500,         // Flash duration in ms (1–60000). Default: 500
  dashboardPosition: 'bottom-right',  // 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
  renderThreshold: 3,         // Min consecutive wasted renders before memo suggestions appear
  trackComponents: [],        // If non-empty, only track these component display names
})

Option Reference

| Option | Type | Default | Description | |---|---|---|---| | enabled | boolean | true | Master switch. Set to false to disable everything. | | flashColor | string | "rgba(255,0,0,0.3)" | CSS color applied as outline to wasting DOM nodes. | | flashDuration | number | 500 | Outline visible duration in milliseconds. | | dashboardPosition | DashboardPosition | "bottom-right" | Corner where the floating panel appears. | | renderThreshold | number | 3 | Minimum wasted renders before suggestions are generated. | | trackComponents | string[] | [] | Allowlist by component displayName. Empty = track all. |


API Reference

initPerfScan(options?)

Initializes all instrumentation. Must be called before createRoot().render(). Safe to call multiple times — subsequent calls are no-ops if already initialized.

import { initPerfScan } from 'react-perf-scan'

initPerfScan({ dashboardPosition: 'top-left', renderThreshold: 5 })

destroyPerfScan()

Tears down all instrumentation, unmounts the dashboard, clears all state, and restores the original createRoot / hydrateRoot implementations.

import { destroyPerfScan } from 'react-perf-scan'

// e.g. during hot-module replacement or in tests
destroyPerfScan()

Exported Types

import type {
  PerfScanOptions,    // Options object for initPerfScan()
  DashboardPosition,  // 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
  RenderRecord,       // One committed render observation
  PropDiff,           // Single prop key change between renders
  StateDiff,          // Same shape as PropDiff, for hook state
  MemoSuggestion,     // A generated memoization hint
  SuggestionType,     // 'memo' | 'useMemo' | 'useCallback'
} from 'react-perf-scan'

Restricting to Specific Components

Use trackComponents to focus on a subset of your component tree:

initPerfScan({
  trackComponents: ['ProductCard', 'DataTable', 'ChartWidget'],
})

Only ProductCard, DataTable, and ChartWidget will be tracked. Component names must match the displayName (case-sensitive).


Dashboard

The floating dashboard panel shows:

  • Component name and wasted render count
  • Prop diffs — which props changed (or didn't) between renders
  • State diffs — hook state snapshot deltas (displayed as hook_0, hook_1, …)
  • Memo suggestionsReact.memo, useCallback, useMemo snippets you can copy

Click Clear to reset the list. Click × to minimize the panel to a badge (click the badge to reopen).


How It Works

initPerfScan()
    │
    ├─ patchReactDomClient()      Wraps createRoot/hydrateRoot so every tree
    │                             is wrapped in a <Profiler id="react-perf-scan-root">
    │
    ├─ installDevToolsCommitHook() Subscribes to __REACT_DEVTOOLS_GLOBAL_HOOK__
    │                             .onCommitFiberRoot to be notified after every commit
    │
    ├─ renderTracker.init()       On each commit, walks the fiber tree via
    │                             queueMicrotask, reads memoizedProps / memoizedState,
    │                             computes shallow diffs, detects wasted renders,
    │                             emits events on the internal eventBus
    │
    ├─ visualHighlighter.init()   Listens for 'wasted-render' events and applies
    │                             a CSS outline flash to the component's DOM node
    │
    ├─ memoSuggestionEngine.init() Listens for 'wasted-render' events, analyzes
    │                              prop/state change patterns, and emits suggestions
    │                              when renderThreshold is reached
    │
    └─ mountDashboard()           Renders a React app into its own isolated root
                                  that listens for events and shows the panel

Suspense safety: If a component sits under a <Suspense> boundary that is currently showing its fallback, its renders are never classified as wasted. This avoids false positives during loading states.


Production Safety

react-perf-scan uses conditional exports to ship two separate bundles:

| Condition | Bundle | Size | |---|---|---| | development | index.dev.* | Full tracking + dashboard | | production | index.prod.* | No-op stubs only (~0 bytes) | | fallback (unknown bundler) | index.prod.* | No-op stubs (safe default) |

In Vite, Create React App (Webpack), and most modern bundlers, NODE_ENV is automatically used to select the correct condition. No extra configuration needed.


Known Limitations

  • Fiber internals are private. react-perf-scan reads React's internal fiber tree (memoizedProps, memoizedState, etc.) which are not part of the public API and may change in future React versions. This is a known trade-off for dev-time tooling.
  • Hook state shown as hook_0, hook_1, … — React does not expose hook names at runtime. State diffs use positional indices.
  • Requires createRoot. Legacy ReactDOM.render() (React 17 and below) is not supported.

Contributing

# 1. Clone the repo
git clone https://github.com/rzkyerl/react-perf-scan.git
cd react-perf-scan

# 2. Install dependencies (requires pnpm >= 9 and Node >= 18)
pnpm install

# 3. Build the library
pnpm build

# 4. Run the playground
pnpm dev

# 5. Run tests
pnpm test

# 6. Type check
pnpm typecheck

Repository Structure

react-perf-scan/
├── packages/
│   └── react-perf-scan/     ← library source
│       ├── src/
│       │   ├── core/        ← renderTracker, memoSuggestionEngine, devToolsHook, …
│       │   ├── ui/          ← Dashboard, BadgeButton, SuggestionCard, visualHighlighter
│       │   ├── types.ts     ← public TypeScript types
│       │   ├── index.ts     ← dev entry point
│       │   └── index.prod.ts ← production no-op entry point
│       └── dist/            ← build output (generated, not committed)
└── playground/              ← Vite app for manual testing

License

MIT — © 2026 rzkyerl