Install
$ agentstack add skill-anaghkanungo7-agent-skills-react-performance-patterns ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
React Performance Patterns
You are an expert in React performance optimization. You help developers identify performance bottlenecks, implement efficient rendering patterns, and build fast, responsive React applications. Your guidance is based on real-world production experience and current best practices.
Core Performance Principles
1. Measure Before Optimizing
Never optimize blindly. Always profile first:
// Use React DevTools Profiler
import { Profiler } from 'react';
function onRenderCallback(
id: string,
phase: 'mount' | 'update',
actualDuration: number,
baseDuration: number,
startTime: number,
commitTime: number
) {
console.log({ id, phase, actualDuration });
}
Key metrics to track:
- Render count
- Render duration
- Component mount/update time
- Bundle size
- Network waterfall
- Core Web Vitals (LCP, INP, CLS)
2. Avoid Unnecessary Renders
React re-renders when:
- State changes
- Props change
- Parent re-renders
- Context value changes
Your job: minimize wasted renders.
3. Code Split Aggressively
Users shouldn't download code for pages they never visit.
4. Optimize Heavy Operations
Move expensive calculations off the main thread or cache results.
Pattern 1: Memoization
React.memo() - Prevent Component Re-renders
// Before: Re-renders on every parent render
function UserCard({ user }: { user: User }) {
return {user.name};
}
// After: Only re-renders when user prop changes
const UserCard = memo(({ user }: { user: User }) => {
return {user.name};
});
When to use:
- Pure functional components
- Components that render frequently with same props
- Expensive render operations
- List items
When NOT to use:
- Component always receives new props
- Props contain objects/arrays created inline
- Component is already fast
useMemo() - Memoize Expensive Calculations
function DataTable({ data, filters }: Props) {
// Bad: Recalculates on every render
const filtered = data.filter(item =>
filters.every(f => f.fn(item))
);
// Good: Only recalculates when data or filters change
const filtered = useMemo(
() => data.filter(item => filters.every(f => f.fn(item))),
[data, filters]
);
return ;
}
When to use:
- Filtering/sorting large arrays
- Complex calculations
- Derived data that's expensive to compute
- Creating objects/arrays passed as props
Cost/benefit check:
// Not worth it (simple operation)
const doubled = useMemo(() => count * 2, [count]);
// Worth it (expensive operation)
const sorted = useMemo(
() => items.sort((a, b) => expensiveCompare(a, b)),
[items]
);
useCallback() - Memoize Functions
function Parent() {
// Bad: New function on every render
const handleClick = () => {
doSomething();
};
// Good: Stable function reference
const handleClick = useCallback(() => {
doSomething();
}, []);
return ;
}
When to use:
- Passing callbacks to memoized children
- Dependencies in useEffect/useMemo
- Creating stable event handlers
- Working with debounce/throttle
Common mistake:
// Mistake: useCallback with new object in dependency
const handleClick = useCallback(() => {
doSomething(data);
}, [data]); // If 'data' is a new object each render, callback still changes
// Better: Destructure stable values
const { id, name } = data;
const handleClick = useCallback(() => {
doSomething({ id, name });
}, [id, name]);
Pattern 2: Code Splitting & Lazy Loading
Component-Level Code Splitting
import { lazy, Suspense } from 'react';
// Bad: Bundles everything upfront
import HeavyChart from './HeavyChart';
import AdminPanel from './AdminPanel';
// Good: Load on demand
const HeavyChart = lazy(() => import('./HeavyChart'));
const AdminPanel = lazy(() => import('./AdminPanel'));
function Dashboard() {
const [showChart, setShowChart] = useState(false);
return (
setShowChart(true)}>
Show Chart
{showChart && (
}>
)}
);
}
Route-Based Code Splitting
// Next.js: Automatic code splitting by route
// app/dashboard/page.tsx
export default function DashboardPage() {
return ;
}
// React Router: Manual code splitting
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
function App() {
return (
}>
}
/>
}>
}
/>
);
}
Preloading for Better UX
// Preload on hover (faster perceived performance)
function Navigation() {
const handleMouseEnter = () => {
// Preload Dashboard chunk
import('./pages/Dashboard');
};
return (
Dashboard
);
}
Pattern 3: Virtualization (Long Lists)
For lists with 100+ items, render only what's visible.
Using react-window
import { FixedSizeList } from 'react-window';
// Bad: Rendering 10,000 items
function BadList({ items }: { items: Item[] }) {
return (
{items.map(item => (
))}
);
}
// Good: Virtualized (only renders ~20 visible items)
function GoodList({ items }: { items: Item[] }) {
return (
{({ index, style }) => (
)}
);
}
Performance impact:
- 10,000 items without virtualization: 5-10 second render
- 10,000 items with virtualization: 50-100ms render
Dynamic Size Lists
import { VariableSizeList } from 'react-window';
function DynamicList({ items }: { items: Item[] }) {
const getItemSize = (index: number) => {
// Return height based on content
return items[index].type === 'large' ? 120 : 60;
};
return (
{({ index, style }) => (
)}
);
}
Pattern 4: Optimize Context Usage
Problem: Context Causes Unnecessary Re-renders
// Bad: Every consumer re-renders on any state change
const AppContext = createContext(null);
function AppProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState([]);
const value = { user, setUser, theme, setTheme, notifications, setNotifications };
return {children};
}
// This re-renders when ANYTHING in context changes
function UserAvatar() {
const { user } = useContext(AppContext); // Re-renders on theme change!
return ;
}
Solution 1: Split Contexts
// Good: Separate concerns
const UserContext = createContext(null);
const ThemeContext = createContext(null);
const NotificationContext = createContext(null);
// Now components only subscribe to what they need
function UserAvatar() {
const { user } = useContext(UserContext); // Only re-renders on user change
return ;
}
Solution 2: Context Selectors
// Using zustand (or similar library)
import create from 'zustand';
const useStore = create((set) => ({
user: null,
theme: 'light',
setUser: (user) => set({ user }),
setTheme: (theme) => set({ theme }),
}));
// Select only what you need
function UserAvatar() {
const user = useStore((state) => state.user); // Only re-renders on user change
return ;
}
function ThemeSwitcher() {
const theme = useStore((state) => state.theme); // Only re-renders on theme change
return ;
}
Solution 3: Memoize Context Value
function AppProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState(null);
// Prevent new object on every render
const value = useMemo(
() => ({ user, setUser }),
[user]
);
return {children};
}
Pattern 5: Debounce & Throttle
Debounce: Wait for User to Stop Typing
import { useDebouncedCallback } from 'use-debounce';
function SearchInput() {
const [query, setQuery] = useState('');
// Bad: API call on every keystroke
const handleChange = (e: ChangeEvent) => {
const value = e.target.value;
setQuery(value);
searchAPI(value); // 🔥 Too many requests!
};
// Good: Wait 300ms after user stops typing
const debouncedSearch = useDebouncedCallback(
(value: string) => {
searchAPI(value);
},
300
);
const handleChange = (e: ChangeEvent) => {
const value = e.target.value;
setQuery(value);
debouncedSearch(value);
};
return ;
}
Custom debounce hook:
function useDebounce(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
// Usage
function SearchResults() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 300);
useEffect(() => {
if (debouncedQuery) {
searchAPI(debouncedQuery);
}
}, [debouncedQuery]);
return setQuery(e.target.value)} />;
}
Throttle: Limit Execution Frequency
import { useThrottledCallback } from 'use-debounce';
function InfiniteScroll() {
// Bad: Fires hundreds of times while scrolling
const handleScroll = () => {
if (isNearBottom()) {
loadMore();
}
};
// Good: Fires at most once every 200ms
const throttledScroll = useThrottledCallback(
() => {
if (isNearBottom()) {
loadMore();
}
},
200
);
return {/* content */};
}
Pattern 6: Optimize Images
Use Next.js Image Component
import Image from 'next/image';
// Bad: Unoptimized, layout shift, loads all sizes
// Good: Optimized, responsive, lazy loaded
Benefits:
- Automatic WebP/AVIF format
- Responsive image sizes
- Lazy loading by default
- Prevents layout shift (width/height specified)
- Built-in blur placeholder
For Non-Next.js Projects
Pattern 7: Optimize Heavy Computations
Web Workers for CPU-Intensive Tasks
// worker.ts
self.onmessage = (e: MessageEvent) => {
const { data } = e;
// Expensive computation
const result = data.map((item) => {
return complexCalculation(item);
});
self.postMessage(result);
};
// Component.tsx
function DataProcessor({ data }: { data: number[] }) {
const [result, setResult] = useState([]);
useEffect(() => {
const worker = new Worker(new URL('./worker.ts', import.meta.url));
worker.postMessage(data);
worker.onmessage = (e: MessageEvent) => {
setResult(e.data);
};
return () => worker.terminate();
}, [data]);
return ;
}
Incremental Rendering (Time Slicing)
import { useTransition } from 'react';
function SearchResults() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const handleChange = (e: ChangeEvent) => {
const value = e.target.value;
// High priority: Update input immediately
setQuery(value);
// Low priority: Update results (can be interrupted)
startTransition(() => {
setSearchResults(search(value));
});
};
return (
<>
{isPending && }
);
}
Pattern 8: Bundle Optimization
Analyze Your Bundle
# Next.js
ANALYZE=true npm run build
# Create React App
npm install --save-dev webpack-bundle-analyzer
Tree Shaking: Import Only What You Need
// Bad: Imports entire library (500KB)
import _ from 'lodash';
const doubled = _.map(arr, n => n * 2);
// Good: Imports only map function (5KB)
import map from 'lodash-es/map';
const doubled = map(arr, n => n * 2);
// Best: Use native alternatives
const doubled = arr.map(n => n * 2);
Dynamic Imports for Third-Party Libraries
// Bad: Bundles Chart.js upfront (200KB)
import { Chart } from 'chart.js';
// Good: Loads Chart.js only when needed
function ChartComponent({ data }: { data: ChartData }) {
const [Chart, setChart] = useState(null);
useEffect(() => {
import('chart.js').then((module) => {
setChart(() => module.Chart);
});
}, []);
if (!Chart) return ;
return ;
}
Remove Unused Dependencies
# Find unused dependencies
npx depcheck
# Remove unused packages
npm uninstall unused-package
Pattern 9: Avoid Inline Object/Array Creation
The Problem
// Bad: New object on every render
function UserList() {
return (
);
}
Even though MemoizedComponent is memoized, it re-renders because props are new objects.
The Fix
// Good: Stable references
const STYLE = { color: 'red' };
const OPTIONS = ['a', 'b', 'c'];
function UserList() {
return (
);
}
// Or use useMemo for dynamic values
function UserList({ color }: { color: string }) {
const style = useMemo(() => ({ color }), [color]);
return ;
}
Pattern 10: Optimize Forms
Controlled vs Uncontrolled Inputs
// Bad: Re-renders entire form on every keystroke
function Form() {
const [formData, setFormData] = useState({ name: '', email: '' });
const handleChange = (e: ChangeEvent) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
return (
{/* Re-renders on every keystroke! */}
);
}
// Good: Use form libraries (React Hook Form, Formik)
import { useForm } from 'react-hook-form';
function Form() {
const { register, handleSubmit } = useForm();
return (
{/* No unnecessary re-renders */}
);
}
Profiling & Debugging
React DevTools Profiler
- Open React DevTools
- Go to Profiler tab
- Click Record
- Interact with your app
- Stop recording
- Analyze:
- Which components rendered?
- How long did they take?
- Why did they render?
Chrome Performance Tab
- Open Chrome DevTools > Performance
- Click Record
- Interact with your app
- Stop recording
- Analyze:
- Long tasks (> 50ms)
- Layout thrashing
- JavaScript execution time
Lighthouse
# Run Lighthouse audit
npm install -g lighthouse
lighthouse https://yoursite.com --view
Checks:
- Performance score
- First Contentful Paint
- Largest Contentful Paint
- Time to Interactive
- Total Blocking Time
- Cumulative Layout Shift
Performance Budget
Set and enforce budgets:
// performance-budget.json
{
"budgets": [
{
"resourceSizes": [
{ "resourceType": "script", "budget": 300 },
{ "resourceType": "image", "budget": 500 },
{ "resourceType": "stylesheet", "budget": 50 }
],
"resourceCounts": [
{ "resourceType": "third-party", "budget": 10 }
]
}
]
}
Quick Wins Checklist
- [ ] Enable production build (React.StrictMode off)
- [ ] Code split routes
- [ ] Lazy load below-fold components
- [ ] Add React.memo to expensive pure components
- [ ] Use useMemo for heavy calculations
- [ ] Use useCallback for memoized children callbacks
- [ ] Virtualize long lists (100+ items)
- [ ] Debounce search inputs
- [ ] Optimize images (WebP, lazy loading, dimensions)
- [ ] Remove console.logs in production
- [ ] Tree shake imports (lodash-es, not lodash)
- [ ] Remove unused dependencies
- [ ] Use CDN for static assets
- [ ] Enable gzip/brotli compression
- [ ] Implement proper caching headers
Resources
When optimizing React apps, focus on the biggest bottlenecks first. Use profiling tools to identify issues, then apply these patterns systematically. Remember: premature optimization is the root of all evil, but measured, targeted optim
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: anaghkanungo7
- Source: anaghkanungo7/agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.