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

renderspy

v0.1.0

Published

Catch unnecessary re-renders before they catch you

Readme

renderspy

Catch unnecessary re-renders before they catch you.

npm version npm downloads license bundle size

[!WARNING] renderspy is intended for development and profiling only. Remove it from production builds or guard it with process.env.NODE_ENV !== 'production'.


Why renderspy?

React DevTools is powerful, but it requires opening a browser extension and manually hunting for hot components. renderspy is different — it lives inside your app, logs programmatically, and surfaces unnecessary re-renders with zero configuration.

  • No browser extension needed
  • Works in any environment (local, staging, CI)
  • Log render reports programmatically via getReport()
  • Ships with TypeScript types. Supports ESM and CJS.

Install

npm install renderspy
# or
yarn add renderspy
# or
pnpm add renderspy

Peer requirements:

| Peer | Version | |------|---------| | react | >=17.0.0 | | react-dom | >=17.0.0 |

React 17+ is required because renderspy uses the React Profiler API introduced in that release.


Quick Start

import { RenderSpy, RenderSpyDashboard } from "renderspy";

function App() {
  return (
    <>
      <RenderSpy threshold={16}>
        <YourApp />
      </RenderSpy>

      {/* Floating dashboard — remove before shipping to production */}
      <RenderSpyDashboard />
    </>
  );
}

Open your app. The floating panel appears in the corner showing live render counts, unnecessary renders, and timing per component.


Core API

<RenderSpy>

Wrap any part of your React tree to start collecting render metrics.

type RenderSpyProps = {
  children: React.ReactNode;
  threshold?: number;          // ms before a render is flagged as slow (default: 16)
  onSlowRender?: (componentName: string, renderTime: number) => void;
  enabled?: boolean;           // default: true
};

Behavior:

  • Records both mount and update renders
  • Flags a render as "unnecessary" when every prop passes referential equality (===)
  • Calls onSlowRender for update renders that exceed threshold

withRenderSpy(Component, options?)

HOC form for tracking a single component without restructuring your tree.

import { withRenderSpy } from "renderspy";

const TrackedUserCard = withRenderSpy(UserCard, { threshold: 8 });
const TrackedCounter  = withRenderSpy(Counter);

Includes:

  • Ref forwarding
  • Preserves original displayName
  • Static property hoisting via hoist-non-react-statics

Type signature:

function withRenderSpy<T extends object>(
  WrappedComponent: React.ComponentType<T>,
  options?: { threshold?: number }
): React.ForwardRefExoticComponent<
  React.PropsWithoutRef<T> & React.RefAttributes<unknown>
>;

<RenderSpyDashboard>

A floating, draggable overlay that shows live render stats.

type RenderSpyDashboardProps = {
  defaultOpen?: boolean;       // default: true
  defaultMinimized?: boolean;  // default: false
};

Features:

  • Fixed, draggable panel — won't interfere with your layout
  • Auto-refreshes every 1000ms
  • Color-coded rows highlight components with wasted renders
  • Clear and Refresh controls

getReport()

Read all collected stats at any time. Useful for logging, assertions, or CI checks.

import { getReport } from "renderspy";

const report = getReport();
// Returns ComponentStats[]
type ComponentStats = {
  componentName: string;
  renderCount: number;
  unnecessaryRenders: number;
  totalTime: number;       // ms
  averageTime: number;     // ms
  lastRenderTime: number;  // ms
};

Example — log report on demand:

import { RenderSpy, getReport } from "renderspy";

function Demo() {
  return (
    <>
      <RenderSpy>
        <App />
      </RenderSpy>
      <button onClick={() => console.table(getReport())}>
        Log render report
      </button>
    </>
  );
}

clearReport()

Reset all collected metrics back to zero.

import { clearReport } from "renderspy";

clearReport();

recordRender() (advanced)

Low-level function used internally by RenderSpy and withRenderSpy. Exported for custom integrations if you need to track renders outside of the standard wrappers.


All Exports

import {
  RenderSpy,
  withRenderSpy,
  RenderSpyDashboard,
  getReport,
  clearReport,
  recordRender,
} from "renderspy";

Type-only imports:

import type {
  ComponentStats,
  RenderSpyProps,
  RenderSpyDashboardProps,
} from "renderspy";

How It Works

| What | How | |------|-----| | Render timing | React Profiler actualDuration | | Unnecessary render detection | Prop-by-prop === comparison | | Report sorting | By unnecessaryRenders descending |


CI/CD Example (Render Regression Gate)

You can fail CI when critical components start introducing unnecessary renders.

1) Add an integration-style test

// dashboard-render.test.tsx
import React from "react";
import { render } from "@testing-library/react";
import { RenderSpy, clearReport, getReport } from "renderspy";

type HeaderProps = { title: string };
const Header: React.FC<HeaderProps> = ({ title }) => <h1>{title}</h1>;

test("Header should keep unnecessary renders low", () => {
  clearReport();

  const { rerender } = render(
    <RenderSpy>
      <Header title="renderspy" />
    </RenderSpy>
  );

  // Same props on rerender should be tracked as unnecessary.
  rerender(
    <RenderSpy>
      <Header title="renderspy" />
    </RenderSpy>
  );

  const report = getReport();
  const header = report.find((item) => item.componentName === "Header");

  expect(header).toBeDefined();
  expect(header?.unnecessaryRenders).toBeLessThanOrEqual(1);
});

2) Run it in GitHub Actions

# .github/workflows/ci.yml
name: CI

on:
  pull_request:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm run build
      - run: npm test -- --runInBand

Tip: start with high-value components (layout, nav, large lists), then tighten limits over time.


License

MIT © Meezan Shaikh