Performance testing utilities for React components and hooks with sync/async update tracking in Vitest
npm install vitest-react-profiler


















React component render tracking and performance testing utilities for Vitest
๐ Documentation โข ๐ Quick Start โข ๐ API Reference โข ๐ฌ Discussions
---
- ๐ Precise Render Tracking - Count exact number of renders with zero guesswork
- โก Performance Monitoring - Detect unnecessary re-renders and track component behavior
- ๐ฏ Phase Detection - Distinguish between mount, update, and nested update phases
- ๐ธ Snapshot API - Create render baselines with snapshot() and measure deltas with Extended Matchers
- ๐ช Hook Profiling - Profile custom hooks with full Context support via wrapper option
- โฑ๏ธ Async Testing - Subscribe to renders with onRender() and wait with waitForNextRender()
- ๐ Real-Time Notifications - React to renders immediately with event-based subscriptions
- โ๏ธ React 18+ Concurrent Ready - Full support for useTransition and useDeferredValue
- ๐งน True Automatic Cleanup - Zero boilerplate! Components auto-clear between tests
- ๐ Zero Config - Works out of the box with Vitest and React Testing Library
- ๐ก๏ธ Built-in Safety Mechanisms - Automatic detection of infinite render loops and memory leaks
- ๐ช Full TypeScript Support - Complete type safety with custom Vitest matchers
- ๐งฌ Battle-Tested Quality - 100% mutation score, property-based testing, stress tests, SonarCloud verified.
- ๐ฌ Mathematically Verified - 266 property tests with 140,000+ randomized scenarios per run
- ๐๏ธ Stress-Tested - 34 stress tests validate performance on 10,000-render histories
- ๐ Performance Baselines - 46 benchmarks establish regression detection metrics
Building a UI-kit for your project or company? You need to track, measure, and improve component performance. This tool helps you:
- Catch unnecessary re-renders during development
- Set performance budgets for components
- Document performance characteristics in tests
Publishing React components? It's critical to prove your solution is optimized and won't degrade performance in user projects. With this tool, you can:
- Add performance tests to CI/CD pipelines
- Showcase performance metrics in documentation
- Track performance regressions between releases
Have strict performance requirements (fintech, healthcare, real-time systems)? The tool allows you to:
- Set thresholds for render counts
- Automatically verify SLA compliance in tests
- Track asynchronous state updates
---
``bash`
npm install --save-dev vitest-react-profileror
yarn add -D vitest-react-profileror
pnpm add -D vitest-react-profiler
`typescript`
// vitest-setup.ts
import "vitest-react-profiler"; // Auto-registers afterEach cleanup
Configure Vitest:
`typescript
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "jsdom",
setupFiles: ["./vitest-setup.ts"],
},
});
`
`typescript
import { render } from '@testing-library/react';
import { withProfiler } from 'vitest-react-profiler';
import { MyComponent } from './MyComponent';
it('should render only once on mount', () => {
const ProfiledComponent = withProfiler(MyComponent);
render(
expect(ProfiledComponent).toHaveRenderedTimes(1);
expect(ProfiledComponent).toHaveMountedOnce();
});
`
---
Test components with asynchronous state updates using event-based utilities.
`typescript
const AsyncComponent = () => {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, []);
return {data ?? "Loading..."};
};
it('should handle async updates', async () => {
const Profiled = withProfiler(AsyncComponent);
render(
// Wait for mount + async update
await expect(Profiled).toEventuallyRenderTimes(2);
});
`
- toEventuallyRenderTimes(n) - Wait for exact render count
- toEventuallyRenderAtLeast(n) - Wait for minimum renders
- toEventuallyReachPhase(phase) - Wait for specific phase
๐ Read the complete guide โ
---
Wait for components to "stabilize" - useful for virtualized lists, debounced search, and animations.
- waitForStabilization(options) - Wait for renders to stop (debounce pattern)
- toEventuallyStabilize(options) - Matcher version for cleaner assertions
๐ Read the complete guide โ
---
Profile custom hooks with full Context support.
`typescript
import { profileHook } from 'vitest-react-profiler';
const useCounter = (initial: number) => {
const [count, setCount] = useState(initial);
return { count, increment: () => setCount(c => c + 1) };
};
it('should track hook renders', () => {
const { result, profiler } = profileHook(() => useCounter(0));
expect(profiler).toHaveRenderedTimes(1);
act(() => result.current.increment());
expect(profiler).toHaveRenderedTimes(2);
expect(result.current.count).toBe(1);
});
`
`typescript`
const { result, profiler } = profileHook(() => useTheme(), {
wrapper: ({ children }) => (
),
});
๐ Read the complete guide โ
---
Full support for React 18+ Concurrent rendering features - no special configuration needed!
The library automatically tracks renders from:
Test components using transitions for non-urgent updates
Test components using deferred values for performance optimization
The library uses React's built-in API, which automatically handles Concurrent mode:
- โ
Transitions are tracked as regular renders
- โ
Deferred values trigger additional renders (as expected)
- โ
Interrupted renders are handled correctly by React
- โ
No special configuration or setup required
Note: The library tracks renders, not React's internal scheduling.
Concurrent Features work transparently - your tests verify component behavior, not React internals.
๐ Read the complete guide โ
---
Create render baselines and measure deltas for optimization testing.
`typescript
const ProfiledCounter = withProfiler(Counter);
render(
ProfiledCounter.snapshot(); // Create baseline
fireEvent.click(screen.getByText('Increment'));
expect(ProfiledCounter).toHaveRerenderedOnce(); // Verify single rerender
`
`typescript
const ProfiledList = withProfiler(MemoizedList);
const { rerender } = render(
ProfiledList.snapshot();
rerender(
expect(ProfiledList).toNotHaveRerendered(); // Memo prevented rerender
`
`typescript
// Sync matchers
expect(ProfiledComponent).toHaveRerendered(); // At least one rerender
expect(ProfiledComponent).toHaveRerendered(3); // Exactly 3 rerenders
// Async matchers - wait for rerenders
await expect(ProfiledComponent).toEventuallyRerender();
await expect(ProfiledComponent).toEventuallyRerenderTimes(2, { timeout: 2000 });
`
| Method/Matcher | Description |
|----------------|-------------|
| snapshot() | Mark baseline for render counting |getRendersSinceSnapshot()
| | Get number of renders since baseline |toHaveRerenderedOnce()
| | Assert exactly one rerender |toNotHaveRerendered()
| | Assert no rerenders |toHaveRerendered()
| | Assert at least one rerender |toHaveRerendered(n)
| | Assert exactly n rerenders |toEventuallyRerender()
| | Wait for rerender |toEventuallyRerenderTimes(n)
| | Wait for exact count |
๐ Read the complete guide โ
---
๐ Full documentation is available in the Wiki
- Architecture Documentation - ๐ Complete technical architecture (15 sections, ~14,000 lines)
- Getting Started Guide - Installation and configuration
- API Reference - Complete API documentation
- Snapshot API - Extended matchers for optimization testing
- Hook Profiling - Testing React hooks
- React 18+ Concurrent Features - useTransition & useDeferredValue
- Examples - Real-world usage patterns
- Best Practices - Tips and recommendations
- Troubleshooting - Common issues and solutions
---
We welcome contributions! Please read our Contributing Guide and Code of Conduct.
`bashRun tests
npm test # Unit/integration tests (811 tests)
npm run test:properties # Property-based tests (266 tests, 140k+ checks)
npm run test:stress # Stress tests (34 tests, large histories)
npm run test:bench # Performance benchmarks (46 benchmarks)
npm run test:mutation # Mutation testing (100% score)
---
MIT ยฉ Oleg Ivanov
---
Made with โค๏ธ by the community
Report Bug โข Request Feature โข Discussions