(
function ExpensiveComponent({ data, onAction }) {
// Complex rendering logic
return ;
}
);
```
**When to use React.memo:**
- Component renders frequently
- Component has expensive rendering
- Props don't change often
- Component is a list item
- DataGrid cells/renderers
**When NOT to use React.memo:**
- Props change frequently anyway
- Rendering is already fast
- Premature optimization
---
## Debounced Search
### Using use-debounce Hook
```typescript
import { useState } from 'react';
import { useDebounce } from 'use-debounce';
import { useSuspenseQuery } from '@tanstack/react-query';
export const SearchComponent: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('');
// Debounce for 300ms
const [debouncedSearchTerm] = useDebounce(searchTerm, 300);
// Query uses debounced value
const { data } = useSuspenseQuery({
queryKey: ['search', debouncedSearchTerm],
queryFn: () => api.search(debouncedSearchTerm),
enabled: debouncedSearchTerm.length > 0,
});
return (
setSearchTerm(e.target.value)}
placeholder='Search...'
/>
);
};
```
**Optimal Debounce Timing:**
- **300-500ms**: Search/filtering
- **1000ms**: Auto-save
- **100-200ms**: Real-time validation
---
## Memory Leak Prevention
### Cleanup Timeouts/Intervals
```typescript
import { useEffect, useState } from 'react';
export const MyComponent: React.FC = () => {
const [count, setCount] = useState(0);
useEffect(() => {
// ✅ CORRECT - Cleanup interval
const intervalId = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => {
clearInterval(intervalId); // Cleanup!
};
}, []);
useEffect(() => {
// ✅ CORRECT - Cleanup timeout
const timeoutId = setTimeout(() => {
console.log('Delayed action');
}, 5000);
return () => {
clearTimeout(timeoutId); // Cleanup!
};
}, []);
return {count}
;
};
```
### Cleanup Event Listeners
```typescript
useEffect(() => {
const handleResize = () => {
console.log('Resized');
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize); // Cleanup!
};
}, []);
```
### Abort Controllers for Fetch
```typescript
useEffect(() => {
const abortController = new AbortController();
fetch('/api/data', { signal: abortController.signal })
.then(response => response.json())
.then(data => setState(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
}
});
return () => {
abortController.abort(); // Cleanup!
};
}, []);
```
**Note**: With TanStack Query, this is handled automatically.
---
## Form Performance
### Watch Specific Fields (Not All)
```typescript
import { useForm } from 'react-hook-form';
export const MyForm: React.FC = () => {
const { register, watch, handleSubmit } = useForm();
// ❌ AVOID - Watches all fields, re-renders on any change
const formValues = watch();
// ✅ CORRECT - Watch only what you need
const username = watch('username');
const email = watch('email');
// Or multiple specific fields
const [username, email] = watch(['username', 'email']);
return (
);
};
```
---
## List Rendering Optimization
### Key Prop Usage
```typescript
// ✅ CORRECT - Stable unique keys
{items.map(item => (
{item.name}
))}
// ❌ AVOID - Index as key (unstable if list changes)
{items.map((item, index) => (
// WRONG if list reorders
{item.name}
))}
```
### Memoized List Items
```typescript
const ListItem = React.memo(({ item, onAction }) => {
return (
onAction(item.id)}>
{item.name}
);
});
export const List: React.FC<{ items: Item[] }> = ({ items }) => {
const handleAction = useCallback((id: string) => {
console.log('Action:', id);
}, []);
return (
{items.map(item => (
))}
);
};
```
---
## Preventing Component Re-initialization
### The Problem
```typescript
// ❌ AVOID - Component recreated on every render
export const Parent: React.FC = () => {
// New component definition each render!
const ChildComponent = () => Child
;
return ; // Unmounts and remounts every render
};
```
### The Solution
```typescript
// ✅ CORRECT - Define outside or use useMemo
const ChildComponent: React.FC = () => Child
;
export const Parent: React.FC = () => {
return ; // Stable component
};
// ✅ OR if dynamic, use useMemo
export const Parent: React.FC<{ config: Config }> = ({ config }) => {
const DynamicComponent = useMemo(() => {
return () => {config.title}
;
}, [config.title]);
return ;
};
```
---
## Lazy Loading Heavy Dependencies
### Code Splitting
```typescript
// ❌ AVOID - Import heavy libraries at top level
import jsPDF from 'jspdf'; // Large library loaded immediately
import * as XLSX from 'xlsx'; // Large library loaded immediately
// ✅ CORRECT - Dynamic import when needed
const handleExportPDF = async () => {
const { jsPDF } = await import('jspdf');
const doc = new jsPDF();
// Use it
};
const handleExportExcel = async () => {
const XLSX = await import('xlsx');
// Use it
};
```
---
## Summary
**Performance Checklist:**
- ✅ `useMemo` for expensive computations (filter, sort, map)
- ✅ `useCallback` for functions passed to children
- ✅ `React.memo` for expensive components
- ✅ Debounce search/filter (300-500ms)
- ✅ Cleanup timeouts/intervals in useEffect
- ✅ Watch specific form fields (not all)
- ✅ Stable keys in lists
- ✅ Lazy load heavy libraries
- ✅ Code splitting with React.lazy
**See Also:**
- [component-patterns.md](component-patterns.md) - Lazy loading
- [data-fetching.md](data-fetching.md) - TanStack Query optimization
- [complete-examples.md](complete-examples.md) - Performance patterns in context