npm package: @moraisdeoliveirawesley/react-performance-guard
Description: React performance library that automatically detects unnecessary re-renders, mutable props, heavy components, and more
Latest version: 0.0.3

# react-performance-guard

[![npm version](https://img.shields.io/npm/v/@moraisdeoliveirawesley/react-performance-guard)](https://www.npmjs.com/package/@moraisdeoliveirawesley/react-performance-guard)
[![npm downloads](https://img.shields.io/npm/dm/@moraisdeoliveirawesley/react-performance-guard)](https://www.npmjs.com/package/@moraisdeoliveirawesley/react-performance-guard)
[![GitHub Repo stars](https://img.shields.io/github/stars/wmoraisoliveira/react-performance-guard?style=social)](https://github.com/wmoraisoliveira/react-performance-guard)

**React performance library** that automatically detects common issues and **points to where they occur** (file:line via stack trace). Use it in development to fix unnecessary re-renders, mutable props, heavy components, render storms, inline objects, large lists, setState after unmount, and more.

## Features

| Detector | What it catches | Where it points |
|----------|------------------|------------------|
| **Unnecessary re-render** | Same props (shallow) but component re-rendered | Component that re-rendered (use `React.memo` or move state down) |
| **Mutable props** | Props object mutated after being passed | Component that received mutated props |
| **Heavy component** | Commit phase > threshold (e.g. 16ms) | Component (or tree) under `<PerfGuardBoundary>` |
| **Render storm** | Too many re-renders in a short window | Component that stormed |
| **Possible setState in render** | Synchronous re-renders (e.g. 5 in &lt;5ms) | Component that likely calls setState during render |
| **Inline objects/functions** | New object/function references in props every render | Component receiving inline props (parent should use `useMemo`/`useCallback`) |
| **Large list** | Rendering many items without virtualization | Where `useLargeListCheck(length)` was called |
| **setState after unmount** | setState called after component unmounted | Component that used `useSafeState` and triggered after unmount |

Every reported issue includes optional **`stackTrace`** and **`location`** (parsed `file`, `line`, `column`) so you can jump straight to the problematic code.

## Install

```bash
npm install react-perf-guard
# or
yarn add react-perf-guard
pnpm add react-perf-guard
```

**Peer dependency:** React 16.8+ (hooks).

## Quick start

### 1. Optional: global config (e.g. in app root)

```tsx
import { configurePerfGuard } from 'react-perf-guard';

configurePerfGuard({
  onIssue: (issue) => {
    // issue.location => { file, line, column } — where the problem was detected
    console.warn('[Perf]', issue.category, issue.message, issue.location);
  },
});
```

### 2. Per-component: hook (recommended)

```tsx
import { usePerfGuard } from 'react-perf-guard';

function MyCard({ title, count }: { title: string; count: number }) {
  usePerfGuard({ title, count }, { componentName: 'MyCard' });
  return <div>{title}: {count}</div>;
}
```

This enables: re-render, mutable props, render storm, and **inline props** detection. In the console you’ll see the **location** (file:line) when an issue is found.

### 3. Heavy components: `<PerfGuardBoundary>`

```tsx
import { PerfGuardBoundary } from 'react-perf-guard';

<PerfGuardBoundary id="ProductList" heavyThresholdMs={16}>
  <ProductList />
</PerfGuardBoundary>
```

### 4. Large lists: `useLargeListCheck`

```tsx
import { useLargeListCheck } from 'react-perf-guard';

function ProductList({ items }: { items: Product[] }) {
  useLargeListCheck(items.length, { componentName: 'ProductList', threshold: 50 });
  return (
    <ul>
      {items.map((p) => <li key={p.id}>{p.name}</li>)}
    </ul>
  );
}
```

### 5. Avoid setState after unmount: `useSafeState`

```tsx
import { useSafeState } from 'react-perf-guard';

function Search() {
  const [query, setQuery] = useSafeState('', { componentName: 'Search' });
  useEffect(() => {
    const id = setTimeout(() => fetchResults(query).then(setResults), 300);
    return () => clearTimeout(id);
  }, [query]);
  // If fetchResults resolves after unmount, setState would run — useSafeState warns and no-ops
  return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
}
```

### 6. HOC: `withPerfGuard`

```tsx
import { withPerfGuard } from 'react-perf-guard';

const MyCard = withPerfGuard(
  function MyCard({ title }: { title: string }) {
    return <div>{title}</div>;
  },
  { componentName: 'MyCard' }
);
```

## Where is the problem?

Each `PerfIssue` can include:

- **`location`** — `{ file?, line?, column?, functionName?, raw? }` — parsed from the stack (first frame that isn’t react-perf-guard or React).
- **`stackTrace`** — full `Error.stack` string.

So in `onIssue` you can do:

```tsx
onIssue: (issue) => {
  if (issue.location?.file) {
    console.warn(`${issue.category} at ${issue.location.file}:${issue.location.line}:${issue.location.column}`);
  }
  console.warn(issue.message, issue.details);
}
```

Stack capture is on by default. To disable (e.g. in production): `setCaptureStack(false)`.

## API

### Hooks

- **`usePerfGuard(props, options?)`** — Re-render, mutable props, render storm, inline props. Pass props + optional `componentName`, `onIssue`, `disabled`, or per-detector options.
- **`useReRenderDetection(props, options?)`** — Only re-render (same props, shallow).
- **`useMutablePropsDetection(props, options?)`** — Only mutable props.
- **`useRenderStormDetection(options?)`** — Only render storm (and possible setState-in-render when re-renders are synchronous).
- **`useInlinePropsDetection(props, options?)`** — Only new object/function references in props.
- **`useLargeListCheck(itemCount, options?)`** — Warns when `itemCount` ≥ threshold (default 50). Use in components that render long lists.
- **`useSafeState(initialState, options?)`** — Like `useState`; warns and no-ops if setState is called after unmount.
- **`useHeavyComponentReport(options?)`** — Returns `onRender` for `<Profiler>`. Used by `PerfGuardBoundary`.

### Components

- **`<PerfGuardBoundary id="..." heavyThresholdMs={16} onIssue={...}>`** — Wraps children in `Profiler`, reports when commit duration exceeds threshold.
- **`withPerfGuard(Component, options?)`** — HOC that injects `usePerfGuard`.

### Config / utils

- **`configurePerfGuard(config)`** — Global `onIssue`, thresholds, etc.
- **`setOnIssue(cb)`** — Set or clear global issue callback.
- **`setCaptureStack(enabled)`** — Turn stack/location capture on or off (default on).
- **`report(issue)`** — Manually report a `PerfIssue`.
- **`createAndReport(issue)`** — Like `report` but enriches with stack/location (used by detectors).
- **`captureStackTrace()`** / **`parseStackLocation(stack)`** / **`captureLocation()`** / **`enrichWithLocation(issue)`** — Low-level stack/location helpers.

### Types

- **`PerfIssue`** — `{ category, severity, message, componentName?, details?, timestamp, stackTrace?, location? }`
- **`SourceLocation`** — `{ file?, line?, column?, functionName?, raw? }`
- **`IssueCategory`** — `'unnecessary-re-render' | 'mutable-props' | 'heavy-component' | 'render-storm' | 'inline-objects' | 'large-list' | 'setState-after-unmount' | 'possible-setState-in-render'`
- **`OnIssueCallback`** — `(issue: PerfIssue) => void`

## Collecting issues for O1 / evidence

```tsx
const issues: PerfIssue[] = [];
configurePerfGuard({
  onIssue: (issue) => {
    issues.push(issue);
    if (process.env.NODE_ENV === 'development') {
      console.warn('[react-perf-guard]', issue.category, issue.message, issue.location);
    }
  },
});
// Later: export or send `issues` (including location) for documentation/evidence
```

## License

MIT.