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
wrapperoption - β±οΈ Async Testing - Subscribe to renders with
onRender()and wait withwaitForNextRender() - π Real-Time Notifications - React to renders immediately with event-based subscriptions
- βοΈ React 18+ Concurrent Ready - Full support for
useTransitionanduseDeferredValue - π§Ή 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
npm install --save-dev vitest-react-profiler
# or
yarn add -D vitest-react-profiler
# or
pnpm add -D vitest-react-profiler// vitest-setup.ts
import "vitest-react-profiler"; // Auto-registers afterEach cleanupConfigure Vitest:
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "jsdom",
setupFiles: ["./vitest-setup.ts"],
},
});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(<ProfiledComponent />);
expect(ProfiledComponent).toHaveRenderedTimes(1);
expect(ProfiledComponent).toHaveMountedOnce();
});Test components with asynchronous state updates using event-based utilities.
const AsyncComponent = () => {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, []);
return <div>{data ?? "Loading..."}</div>;
};
it('should handle async updates', async () => {
const Profiled = withProfiler(AsyncComponent);
render(<Profiled />);
// Wait for mount + async update
await expect(Profiled).toEventuallyRenderTimes(2);
});toEventuallyRenderTimes(n)- Wait for exact render counttoEventuallyRenderAtLeast(n)- Wait for minimum renderstoEventuallyReachPhase(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.
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);
});const { result, profiler } = profileHook(() => useTheme(), {
wrapper: ({ children }) => (
<ThemeProvider theme="dark">{children}</ThemeProvider>
),
});π 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 <Profiler> 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.
const ProfiledCounter = withProfiler(Counter);
render(<ProfiledCounter />);
ProfiledCounter.snapshot(); // Create baseline
fireEvent.click(screen.getByText('Increment'));
expect(ProfiledCounter).toHaveRerenderedOnce(); // Verify single rerenderconst ProfiledList = withProfiler(MemoizedList);
const { rerender } = render(<ProfiledList items={items} theme="light" />);
ProfiledList.snapshot();
rerender(<ProfiledList items={items} theme="dark" />);
expect(ProfiledList).toNotHaveRerendered(); // Memo prevented rerender// 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.
# Run 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)
# Build
npm run buildMIT Β© Oleg Ivanov
Made with β€οΈ by the community
Report Bug β’ Request Feature β’ Discussions