React Performance Optimization: Practical Techniques That Actually Work

React Performance Optimization: Practical Techniques That Actually Work

React is fast by default, but as your application grows, performance can become an issue. The good news? Most performance problems have straightforward solutions once you know what to look for.

Measure First, Optimize Second

Never optimize without measuring. Use React DevTools Profiler to identify actual bottlenecks:

import { Profiler } from 'react';

function onRenderCallback(id, phase, actualDuration) {
  console.log(`${id} (${phase}) took ${actualDuration}ms`);
}

<Profiler id="MyComponent" onRender={onRenderCallback}>
  <MyComponent />
</Profiler>

Memoization Strategies

React.memo()

Prevent unnecessary re-renders of component:

const ExpensiveComponent = React.memo(({ data }) => {
  return <div>{/* Expensive rendering */}</div>;
});

// Only re-renders if data changes

useMemo()

Memoize expensive calculations:

function DataTable({ data, filter }) {
  const filteredData = useMemo(() => {
    return data.filter(item => item.category === filter);
  }, [data, filter]); // Only recalculates when data or filter changes
  
  return <Table data={filteredData} />;
}

useCallback()

Memoize function references:

function ParentComponent() {
  const handleClick = useCallback((id) => {
    console.log('Clicked:', id);
  }, []); // Function reference stays the same
  
  return items.map(item => (
    <MemoizedChild key={item.id} onClick={handleClick} />
  ));
}

Code Splitting

Don't load code users don't need:

import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

Virtualize Long Lists

Don't render 10,000 items—render what's visible:

import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>{items[index].name}</div>
      )}
    </FixedSizeList>
  );
}

Optimize Context Usage

Avoid re-rendering every consumer:

// ❌ Bad: Everything re-renders when any value changes
const AppContext = createContext();

function Provider({ children }) {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  
  return (
    <AppContext.Provider value={{ user, setUser, theme, setTheme }}>
      {children}
    </AppContext.Provider>
  );
}

// ✅ Good: Split contexts by concern
const UserContext = createContext();
const ThemeContext = createContext();

function Provider({ children }) {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  
  return (
    <UserContext.Provider value={{ user, setUser }}>
      <ThemeContext.Provider value={{ theme, setTheme }}>
        {children}
      </ThemeContext.Provider>
    </UserContext.Provider>
  );
}

Debounce Expensive Operations

import { useDeferredValue } from 'react';

function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);
  
  const results = useMemo(
    () => expensiveSearch(deferredQuery),
    [deferredQuery]
  );
  
  return <ResultsList results={results} />;
}

Use Keys Correctly

Keys help React identify which items changed:

// ❌ Bad: Index as key causes issues with reordering
{items.map((item, index) => (
  <Item key={index} data={item} />
))}

// ✅ Good: Stable unique ID
{items.map(item => (
  <Item key={item.id} data={item} />
))}

Avoid Inline Object/Array Creation

// ❌ Bad: Creates new object every render
<Component style={{ margin: 10 }} />
<Component items={[1, 2, 3]} />

// ✅ Good: Stable reference
const style = { margin: 10 };
const items = [1, 2, 3];

<Component style={style} />
<Component items={items} />

Optimize Images

// Use next/image or similar for automatic optimization
import Image from 'next/image';

<Image
  src="/photo.jpg"
  width={800}
  height={600}
  loading="lazy"
  placeholder="blur"
/>

State Colocation

Keep state as close as possible to where it's used:

// ❌ Bad: State at top level causes entire tree to re-render
function App() {
  const [hoveredId, setHoveredId] = useState(null);
  return <UserList users={users} hoveredId={hoveredId} setHoveredId={setHoveredId} />;
}

// ✅ Good: State in the component that needs it
function UserCard({ user }) {
  const [isHovered, setIsHovered] = useState(false);
  return <div onMouseEnter={() => setIsHovered(true)}>{/* ... */}</div>;
}

Use Production Builds

Development builds are much slower:

# Development (slow, includes debugging)
npm start

# Production (fast, optimized)
npm run build
npm run preview

Lazy Load Images

<img 
  src="image.jpg" 
  loading="lazy" 
  decoding="async"
  alt="Description"
/>

Web Workers for Heavy Computation

Move heavy computation off the main thread:

import { useEffect, useState } from 'react';

function DataProcessor({ data }) {
  const [result, setResult] = useState(null);
  
  useEffect(() => {
    const worker = new Worker('./processor.worker.js');
    worker.postMessage(data);
    worker.onmessage = (e) => setResult(e.data);
    
    return () => worker.terminate();
  }, [data]);
  
  return <Display result={result} />;
}

The 80/20 Rule

Focus on the biggest wins:

  1. Code splitting - Usually the biggest impact
  2. Virtualization - For long lists
  3. Image optimization - Often overlooked
  4. Memoization - Only where it matters

Don't micro-optimize everything. Profile, find the bottleneck, fix it, measure again.

Performance Budget

Set and enforce performance budgets:

  • Initial load: < 3 seconds
  • Time to Interactive: < 5 seconds
  • Lighthouse score: > 90

Use tools like Lighthouse and WebPageTest to measure.

Keep Learning

React is constantly evolving. Stay updated with:

Need help optimizing your React app? Get in touch.