React Performance Optimization Techniques
Hey! Debmalya Biswas here. Today I'm sharing advanced React performance optimization techniques I use to build fast, responsive applications like the Debmalya Biswas portfolio.
Understanding React Performance
As Debmalya Biswas, frontend developer, I've learned that React performance comes down to:
- Minimizing re-renders
- Reducing bundle size
- Optimizing expensive operations
- Efficient data fetching
Memoization Techniques
React.memo
Prevent unnecessary re-renders:
interface UserCardProps {
user: User
onSelect: (id: string) => void
}
const UserCard = React.memo(({ user, onSelect }: UserCardProps) => {
return (
<div onClick={() => onSelect(user.id)}>
{user.name}
</div>
)
}, (prevProps, nextProps) => {
// Custom comparison
return prevProps.user.id === nextProps.user.id
})useMemo
Cache expensive computations:
function DataTable({ items }: { items: Item[] }) {
const sortedItems = useMemo(() => {
console.log('Sorting items...')
return [...items].sort((a, b) => a.name.localeCompare(b.name))
}, [items])
const totalValue = useMemo(() => {
return items.reduce((sum, item) => sum + item.value, 0)
}, [items])
return (
<div>
<p>Total: {totalValue}</p>
{sortedItems.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
)
}useCallback
Memoize callback functions:
function TodoList({ todos }: { todos: Todo[] }) {
const [filter, setFilter] = useState('all')
// Memoize callback to prevent child re-renders
const handleToggle = useCallback((id: string) => {
toggleTodo(id)
}, []) // Empty deps if toggleTodo is stable
const handleDelete = useCallback((id: string) => {
deleteTodo(id)
}, [])
return (
<div>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={handleToggle}
onDelete={handleDelete}
/>
))}
</div>
)
}Virtualization
For large lists, use virtualization. In the Debmalya Biswas website, I implement this for data-heavy sections:
import { useVirtualizer } from '@tanstack/react-virtual'
function VirtualList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
overscan: 5,
})
return (
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
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>
)
}Code Splitting
Dynamic Imports
import { lazy, Suspense } from 'react'
// Lazy load heavy components
const HeavyChart = lazy(() => import('./HeavyChart'))
const AdminPanel = lazy(() => import('./AdminPanel'))
function Dashboard() {
return (
<div>
<Suspense fallback={<ChartSkeleton />}>
<HeavyChart />
</Suspense>
<Suspense fallback={<div>Loading...</div>}>
<AdminPanel />
</Suspense>
</div>
)
}Route-Based Splitting
import { lazy } from 'react'
import { BrowserRouter, Route, Routes } from 'react-router-dom'
const Home = lazy(() => import('./pages/Home'))
const Blog = lazy(() => import('./pages/Blog'))
const About = lazy(() => import('./pages/About'))
function App() {
return (
<BrowserRouter>
<Suspense fallback={<PageLoader />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/blog" element={<Blog />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</BrowserRouter>
)
}State Management Optimization
Context Splitting
As Debmalya Biswas, SDE, I split contexts to minimize re-renders:
// Bad - One large context
const AppContext = createContext({ user, theme, settings })
// Good - Split contexts
const UserContext = createContext(user)
const ThemeContext = createContext(theme)
const SettingsContext = createContext(settings)
// Even better - Use selectors
function useUser() {
const context = useContext(AppContext)
return context.user
}Zustand for Global State
import create from 'zustand'
interface Store {
user: User | null
setUser: (user: User) => void
posts: Post[]
addPost: (post: Post) => void
}
const useStore = create<Store>((set) => ({
user: null,
setUser: (user) => set({ user }),
posts: [],
addPost: (post) => set((state) => ({
posts: [...state.posts, post]
})),
}))
// Use only what you need
function UserProfile() {
const user = useStore(state => state.user)
// Only re-renders when user changes
}Debouncing and Throttling
function SearchInput() {
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const debouncedSearch = useMemo(
() =>
debounce(async (searchQuery: string) => {
const data = await searchAPI(searchQuery)
setResults(data)
}, 300),
[]
)
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
setQuery(value)
debouncedSearch(value)
}
return (
<div>
<input value={query} onChange={handleChange} />
<SearchResults results={results} />
</div>
)
}
// Custom debounce hook
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value)
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value)
}, delay)
return () => clearTimeout(timer)
}, [value, delay])
return debouncedValue
}Image Optimization
Used throughout the Debmalya Biswas portfolio:
import Image from 'next/image'
function OptimizedImage() {
return (
<>
{/* Above-fold images */}
<Image
src="/hero.jpg"
width={1200}
height={600}
priority
alt="Hero"
/>
{/* Below-fold images */}
<Image
src="/project.jpg"
width={800}
height={400}
loading="lazy"
alt="Project"
/>
{/* Responsive images */}
<Image
src="/banner.jpg"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
alt="Banner"
/>
</>
)
}useTransition for Concurrent Features
function SearchableList({ items }: { items: Item[] }) {
const [query, setQuery] = useState('')
const [isPending, startTransition] = useTransition()
const filteredItems = useMemo(() => {
return items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
)
}, [items, query])
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
// Mark this update as low priority
startTransition(() => {
setQuery(value)
})
}
return (
<div>
<input
onChange={handleSearch}
placeholder="Search..."
/>
{isPending && <Spinner />}
<List items={filteredItems} />
</div>
)
}Profiling and Measuring
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} took ${actualDuration}ms to ${phase}`)
}
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<Dashboard />
</Profiler>
)
}Custom Performance Hooks
function usePerformance(componentName: string) {
useEffect(() => {
const start = performance.now()
return () => {
const end = performance.now()
console.log(`${componentName} rendered in ${end - start}ms`)
}
})
}
function MyComponent() {
usePerformance('MyComponent')
// Component logic
}Web Workers for Heavy Computation
// worker.ts
self.addEventListener('message', (e) => {
const result = expensiveComputation(e.data)
self.postMessage(result)
})
// Component
function DataProcessor() {
const [result, setResult] = useState(null)
useEffect(() => {
const worker = new Worker('/worker.js')
worker.onmessage = (e) => {
setResult(e.data)
}
worker.postMessage(largeDataset)
return () => worker.terminate()
}, [])
return <div>{result}</div>
}Best Practices Checklist
As Debmalya Biswas, frontend developer, I follow this checklist:
- [ ] Use React.memo for expensive components
- [ ] Implement useMemo for expensive calculations
- [ ] Use useCallback for stable callbacks
- [ ] Implement virtualization for long lists
- [ ] Code split routes and heavy components
- [ ] Optimize images
- [ ] Debounce user inputs
- [ ] Use concurrent features
- [ ] Profile and measure performance
- [ ] Lazy load below-fold content
Real Results
These techniques improved the Debmalya Biswas website:
- Initial load: 1.2s (from 4.5s)
- Time to Interactive: 1.8s (from 5.2s)
- Bundle size: 45KB (from 180KB)
Conclusion
React performance optimization is about making smart trade-offs. Apply these techniques strategically where they matter most.
Keep building fast!
*Debmalya Biswas is a frontend SDE specializing in React performance optimization and modern web development. Visit the Debmalya Biswas portfolio for more insights.*