react-performance
Use when React performance optimization including memoization, lazy loading, and virtualization. Use when optimizing React applications.
What this skill does
# React Performance Optimization
Master react performance optimization for building high-performance, scalable
React applications with industry best practices.
## React.memo and Component Memoization
React.memo prevents unnecessary re-renders by memoizing component output:
```typescript
import { memo } from 'react';
interface Props {
name: string;
onClick: () => void;
}
// Basic memoization
const ExpensiveComponent = memo(
function ExpensiveComponent({ name, onClick }: Props) {
console.log('Rendering ExpensiveComponent');
return <button onClick={onClick}>{name}</button>;
});
// Custom comparison function
const CustomMemo = memo(
function Component({ user }: { user: User }) {
return <div>{user.name}</div>;
},
(prevProps, nextProps) => {
// Return true if passing nextProps would return the same result as prevProps
return prevProps.user.id === nextProps.user.id;
}
);
// When to use custom comparison
const ProductCard = memo(
function ProductCard({ product }: { product: Product }) {
return (
<div>
<h3>{product.name}</h3>
<p>${product.price}</p>
</div>
);
},
(prev, next) => {
// Only re-render if these specific fields change
return (
prev.product.id === next.product.id &&
prev.product.name === next.product.name &&
prev.product.price === next.product.price
);
}
);
```
## useMemo for Expensive Computations
```typescript
import { useMemo, useState } from 'react';
function DataTable({ items }: { items: Item[] }) {
const [filter, setFilter] = useState('');
const [sortBy, setSortBy] = useState<'name' | 'price'>('name');
// Expensive filtering and sorting
const processedItems = useMemo(() => {
console.log('Computing filtered and sorted items');
return items
.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()))
.sort((a, b) => {
if (sortBy === 'name') {
return a.name.localeCompare(b.name);
}
return a.price - b.price;
});
}, [items, filter, sortBy]);
// Expensive aggregate calculation
const statistics = useMemo(() => {
console.log('Computing statistics');
return {
total: processedItems.reduce((sum, item) => sum + item.price, 0),
average: processedItems.length
? processedItems.reduce((sum, item) => sum + item.price, 0) / processedItems.length
: 0,
count: processedItems.length
};
}, [processedItems]);
return (
<>
<input value={filter} onChange={(e) => setFilter(e.target.value)} />
<select value={sortBy} onChange={(e) => setSortBy(e.target.value as any)}>
<option value="name">Name</option>
<option value="price">Price</option>
</select>
<div>Total: ${statistics.total}</div>
<div>Average: ${statistics.average.toFixed(2)}</div>
<div>Count: {statistics.count}</div>
{processedItems.map(item => (
<div key={item.id}>{item.name} - ${item.price}</div>
))}
</>
);
}
```
## useCallback for Stable Function References
```typescript
import { useCallback, useState, memo } from 'react';
// Child component that only re-renders when necessary
const ListItem = memo(function ListItem({
item,
onDelete
}: {
item: Item;
onDelete: (id: string) => void;
}) {
console.log('Rendering ListItem', item.id);
return (
<div>
{item.name}
<button onClick={() => onDelete(item.id)}>Delete</button>
</div>
);
});
function OptimizedList({ items }: { items: Item[] }) {
const [deletedIds, setDeletedIds] = useState<Set<string>>(new Set());
// Without useCallback, this creates a new function on every render
// causing ListItem to re-render even with memo
const handleDelete = useCallback((id: string) => {
setDeletedIds(prev => new Set([...prev, id]));
// API call to delete
api.deleteItem(id);
}, []); // Empty deps means function never changes
const handleDeleteWithDeps = useCallback((id: string) => {
console.log('Already deleted:', deletedIds.size);
setDeletedIds(prev => new Set([...prev, id]));
}, [deletedIds]); // Re-create when deletedIds changes
const visibleItems = items.filter(item => !deletedIds.has(item.id));
return (
<>
{visibleItems.map(item => (
<ListItem key={item.id} item={item} onDelete={handleDelete} />
))}
</>
);
}
```
## Code Splitting with React.lazy and Suspense
```typescript
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
// Lazy load route components
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
const Settings = lazy(() => import('./pages/Settings'));
const Analytics = lazy(() => import('./pages/Analytics'));
// Fallback component
function LoadingSpinner() {
return <div className="spinner">Loading...</div>;
}
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
<Route path="/settings" element={<Settings />} />
<Route path="/analytics" element={<Analytics />} />
</Routes>
</Suspense>
);
}
// Preload on hover for better UX
function Navigation() {
const preloadDashboard = () => import('./pages/Dashboard');
const preloadProfile = () => import('./pages/Profile');
return (
<nav>
<a href="/dashboard" onMouseEnter={preloadDashboard}>Dashboard</a>
<a href="/profile" onMouseEnter={preloadProfile}>Profile</a>
</nav>
);
}
// Nested Suspense boundaries
function DashboardLayout() {
const Header = lazy(() => import('./components/Header'));
const Sidebar = lazy(() => import('./components/Sidebar'));
const Content = lazy(() => import('./components/Content'));
return (
<div className="dashboard">
<Suspense fallback={<div>Loading header...</div>}>
<Header />
</Suspense>
<Suspense fallback={<div>Loading sidebar...</div>}>
<Sidebar />
</Suspense>
<Suspense fallback={<div>Loading content...</div>}>
<Content />
</Suspense>
</div>
);
}
```
## Virtual Scrolling for Large Lists
```typescript
import { FixedSizeList, VariableSizeList } from 'react-window';
import AutoSizer from 'react-virtualized-auto-sizer';
// Fixed size items
function VirtualList({ items }: { items: string[] }) {
const Row = ({ index, style }: {
index: number;
style: React.CSSProperties
}) => (
<div style={style} className="list-item">
{items[index]}
</div>
);
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={35}
width="100%"
>
{Row}
</FixedSizeList>
);
}
// Variable size items
function VariableList({ items }: { items: Post[] }) {
const getItemSize = (index: number) => {
// Calculate height based on content
return items[index].content.length > 100 ? 120 : 80;
};
const Row = ({ index, style }: any) => (
<div style={style} className="post">
<h3>{items[index].title}</h3>
<p>{items[index].content}</p>
</div>
);
return (
<VariableSizeList
height={600}
itemCount={items.length}
itemSize={getItemSize}
width="100%"
>
{Row}
</VariableSizeList>
);
}
// With AutoSizer for responsive layouts
function ResponsiveList({ items }: { items: Item[] }) {
return (
<div style={{ height: '100vh', width: '100%' }}>
<AutoSizer>
{({ height, width }) => (
<FixedSizeList
height={height}
itemCount={items.length}
itemSize={50}
width={width}
>
{({ index, style }) => (
<div style={style}>{items[index].name}</div>
)}
</FixedSizeList>
)}
</AutoSizer>
</div>
);
}
```
## React Profiler API for Performance Related in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.