← Back to list

performance-patterns
by IvanTorresEdge
⭐ 0🍴 1📅 Jan 13, 2026
SKILL.md
name: performance-patterns description: React performance optimization including memoization, code splitting, and lazy loading. Use when optimizing React applications.
React Performance Patterns Skill
This skill covers performance optimization techniques for React applications.
When to Use
Use this skill when:
- Optimizing render performance
- Reducing bundle size
- Implementing lazy loading
- Fixing performance bottlenecks
Core Principle
MEASURE FIRST - Profile before optimizing. Premature optimization leads to complexity without benefit.
Memoization
React.memo
Prevent re-renders when props haven't changed.
import { memo } from 'react';
interface ListItemProps {
item: Item;
onSelect: (id: string) => void;
}
// ✅ Memoize when:
// - Component renders often with same props
// - Component is expensive to render
// - Parent re-renders frequently
const ListItem = memo(function ListItem({
item,
onSelect,
}: ListItemProps): React.ReactElement {
return (
<li onClick={() => onSelect(item.id)}>
{item.name}
</li>
);
});
// With custom comparison
const ListItem = memo(
function ListItem({ item }: ListItemProps): React.ReactElement {
return <li>{item.name}</li>;
},
(prevProps, nextProps) => {
return prevProps.item.id === nextProps.item.id;
}
);
useMemo
Cache expensive computations.
import { useMemo } from 'react';
function UserList({ users, filter }: UserListProps): React.ReactElement {
// ✅ Use for expensive computations
const filteredUsers = useMemo(() => {
return users
.filter((user) => user.name.includes(filter))
.sort((a, b) => a.name.localeCompare(b.name));
}, [users, filter]);
// ❌ Don't use for simple operations
// const fullName = useMemo(() => `${first} ${last}`, [first, last]);
// Just use: const fullName = `${first} ${last}`;
return (
<ul>
{filteredUsers.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
useCallback
Keep function references stable.
import { useCallback, memo } from 'react';
function Parent(): React.ReactElement {
const [count, setCount] = useState(0);
// ✅ Stable callback for memoized children
const handleClick = useCallback((id: string) => {
console.log(`Clicked: ${id}`);
}, []);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>Increment</button>
<MemoizedList onItemClick={handleClick} />
</div>
);
}
const MemoizedList = memo(function MemoizedList({
onItemClick,
}: {
onItemClick: (id: string) => void;
}): React.ReactElement {
console.log('List rendered'); // Only logs once!
return <ul>...</ul>;
});
Code Splitting
React.lazy
import { lazy, Suspense } from 'react';
// Lazy load route components
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Profile = lazy(() => import('./pages/Profile'));
function App(): React.ReactElement {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
);
}
Named Exports with lazy
// For named exports, create intermediate module
// Dashboard.lazy.ts
export { Dashboard as default } from './Dashboard';
// App.tsx
const Dashboard = lazy(() => import('./Dashboard.lazy'));
Prefetching
function NavLink({ to, children }: NavLinkProps): React.ReactElement {
const prefetch = (): void => {
if (to === '/dashboard') {
import('./pages/Dashboard');
} else if (to === '/settings') {
import('./pages/Settings');
}
};
return (
<Link to={to} onMouseEnter={prefetch} onFocus={prefetch}>
{children}
</Link>
);
}
Virtualization
For long lists, render only visible items.
import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';
function VirtualList({ items }: { items: Item[] }): React.ReactElement {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
overscan: 5,
});
return (
<div ref={parentRef} className="h-[500px] overflow-auto">
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
{items[virtualItem.index]?.name}
</div>
))}
</div>
</div>
);
}
State Management Optimization
Split State
// ❌ Single state object causes full re-render
const [state, setState] = useState({
user: null,
theme: 'light',
notifications: [],
});
// ✅ Split state - components only re-render for their state
const [user, setUser] = useState<User | null>(null);
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const [notifications, setNotifications] = useState<Notification[]>([]);
Zustand Selectors
import { create } from 'zustand';
interface Store {
user: User | null;
theme: 'light' | 'dark';
setUser: (user: User) => void;
setTheme: (theme: 'light' | 'dark') => void;
}
const useStore = create<Store>((set) => ({
user: null,
theme: 'light',
setUser: (user) => set({ user }),
setTheme: (theme) => set({ theme }),
}));
// ✅ Subscribe only to what you need
function UserName(): React.ReactElement {
const userName = useStore((state) => state.user?.name);
return <span>{userName}</span>;
}
function ThemeToggle(): React.ReactElement {
const theme = useStore((state) => state.theme);
const setTheme = useStore((state) => state.setTheme);
return <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle</button>;
}
Image Optimization
Lazy Loading Images
function LazyImage({ src, alt }: { src: string; alt: string }): React.ReactElement {
return (
<img
src={src}
alt={alt}
loading="lazy"
decoding="async"
/>
);
}
Next.js Image
import Image from 'next/image';
function OptimizedImage(): React.ReactElement {
return (
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // For LCP images
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>
);
}
Avoiding Common Pitfalls
Object/Array in Dependencies
// ❌ New object each render
function Component({ filters }: { filters: { page: number; limit: number } }): React.ReactElement {
useEffect(() => {
fetchData(filters);
}, [filters]); // Infinite loop!
}
// ✅ Extract primitive values
function Component({ filters }: { filters: { page: number; limit: number } }): React.ReactElement {
const { page, limit } = filters;
useEffect(() => {
fetchData({ page, limit });
}, [page, limit]);
}
Inline Object Props
// ❌ New object each render
<Child style={{ color: 'red' }} />
// ✅ Stable reference
const style = useMemo(() => ({ color: 'red' }), []);
<Child style={style} />
// Or define outside component
const redStyle = { color: 'red' };
<Child style={redStyle} />
Profiling Tools
React DevTools Profiler
- Install React DevTools browser extension
- Open DevTools > Profiler tab
- Click Record, interact with app, stop
- Analyze flame graph for slow renders
Why Did You Render
// In development only
import whyDidYouRender from '@welldone-software/why-did-you-render';
if (process.env.NODE_ENV === 'development') {
whyDidYouRender(React, {
trackAllPureComponents: true,
});
}
Performance Checklist
- Profile before optimizing
- Use React.memo for expensive components
- Implement code splitting for routes
- Virtualize long lists (100+ items)
- Use stable references (useCallback, useMemo)
- Optimize images (lazy loading, proper sizing)
- Use Zustand selectors or split state
- Avoid inline objects/arrays as props
Notes
- Don't optimize prematurely
- memo() is free for simple components
- useCallback/useMemo have overhead - use wisely
- Virtualization significantly helps long lists
Score
Total Score
60/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
Reviews
💬
Reviews coming soon