Code To Learn logo

Code To Learn

M5: Hooks & Performance

M5: Hooks & Performance

Optimize your React app with custom hooks, useMemo, useCallback, and React.memo.

Master advanced React patterns and optimize performance with custom hooks and memoization! ⚡


Overview

Your app works great, but as it grows, you might notice:

  • Repeated logic across components
  • Slow filters or calculations
  • Unnecessary re-renders
  • Components that could be more efficient

This module teaches performance optimization and code reusability - two essential skills for building production-quality React applications.

You'll learn to extract logic into custom hooks, optimize expensive operations, and prevent unnecessary re-renders using React's performance tools.


What You'll Build


Key Concepts

Custom Hooks

Extract reusable logic into custom hooks:

// Before: Repeated fetch logic in every component
function HomePage() {
   const [data, setData] = useState(null);
   const [loading, setLoading] = useState(true);
   const [error, setError] = useState(null);
   
   useEffect(() => {
      // Fetch logic...
   }, []);
}

// After: Reusable custom hook
function useFetch(url) {
   const [data, setData] = useState(null);
   const [loading, setLoading] = useState(true);
   const [error, setError] = useState(null);
   
   useEffect(() => {
      // Fetch logic...
   }, [url]);
   
   return { data, loading, error };
}

// Usage
function HomePage() {
   const { data, loading, error } = useFetch('/api/listings');
}

useMemo

Cache expensive calculations:

// Without useMemo - recalculates every render
function SearchPage({ listings, filters }) {
   const filtered = listings.filter(item => 
      item.price < filters.maxPrice &&
      item.guests >= filters.guests
   );
   // If parent re-renders, this filtering runs again unnecessarily
}

// With useMemo - only recalculates when dependencies change
function SearchPage({ listings, filters }) {
   const filtered = useMemo(() => 
      listings.filter(item => 
         item.price < filters.maxPrice &&
         item.guests >= filters.guests
      ),
      [listings, filters] // Only re-run when these change
   );
}

useCallback

Memoize functions to prevent child re-renders:

// Without useCallback - new function every render
function Parent() {
   const handleClick = () => {
      console.log('clicked');
   };
   
   return <MemoizedChild onClick={handleClick} />;
   // Child re-renders even if nothing changed
}

// With useCallback - stable function reference
function Parent() {
   const handleClick = useCallback(() => {
      console.log('clicked');
   }, []); // Function stays the same
   
   return <MemoizedChild onClick={handleClick} />;
   // Child doesn't re-render unnecessarily
}

React.memo

Prevent component re-renders when props haven't changed:

// Without memo - re-renders every time parent renders
function PropertyCard({ property }) {
   return <div>{property.name}</div>;
}

// With memo - only re-renders if 'property' changes
const PropertyCard = React.memo(function PropertyCard({ property }) {
   return <div>{property.name}</div>;
});

Learning Objectives


Module Roadmap

LessonWhat You'll OptimizeTools/Hooks Used
1-3Data fetchingCustom hooks (useFetch)
4-5Filter calculationsuseMemo
6Event handlersuseCallback
7Component rendersReact.memo
8-9Performance analysisReact DevTools Profiler
10Final reviewBest practices

Prerequisites

Before starting Module 5, ensure:

  • ✅ Completed Module 4
  • ✅ Understand useEffect and useState
  • ✅ Know how to fetch data from APIs
  • ✅ Comfortable with React components and props
  • ✅ Have React DevTools browser extension installed

When to Optimize

⚠️ Don't optimize prematurely!

Optimization adds complexity. Only optimize when you have:

  • Measured performance issues using profiler
  • Identified the bottleneck causing slowness
  • A clear performance goal to achieve

Premature optimization is the root of all evil - Donald Knuth

✅ Optimize when:

  • Expensive calculations in render (complex filtering, sorting)
  • Large lists causing lag
  • Components re-rendering unnecessarily
  • React DevTools Profiler shows high render times
  • Users report sluggish interactions

Performance Checklist

When to use useMemo

✅ Filtering or sorting large arrays
✅ Complex calculations in render
✅ Derived state from expensive operations
❌ Simple values or primitives
❌ Calculations that are already fast

When to use useCallback

✅ Functions passed to memoized child components
✅ Functions in useEffect dependencies
✅ Functions passed to many children
❌ Functions only used in the current component
❌ Event handlers not passed as props

When to use React.memo

✅ Components that re-render often with same props
✅ Components with expensive render logic
✅ List items rendered in .map()
❌ Components that rarely re-render
❌ Components with constantly changing props


Custom Hooks Best Practices

Naming convention:

// ✅ Always start with "use"
function useFetch() {}
function useLocalStorage() {}
function useDebounce() {}

// ❌ Don't skip "use" prefix
function fetchData() {}
function localStorage() {}

Common custom hooks:

// Data fetching
useFetch(url)
useQuery(query)

// Form handling
useForm(initialValues)
useField(name)

// Browser APIs
useLocalStorage(key, initialValue)
useMediaQuery(query)
useClickOutside(ref, handler)

// Utilities
useDebounce(value, delay)
useToggle(initialValue)
usePrevious(value)

React DevTools Profiler

How to use:

  1. Install React DevTools browser extension
  2. Open DevTools → Profiler tab
  3. Click Record button
  4. Interact with your app
  5. Stop recording
  6. Analyze flame graph

What to look for:

🟢 Green bars = Fast render (< 5ms)
🟡 Yellow bars = Moderate (5-15ms)
🔴 Red bars = Slow (> 15ms) - Optimize these!

Optimization wins:

Before:
HomePage: 45ms (with 100 properties)
PropertyCard × 100: 2ms each = 200ms total
Total: 245ms 🔴

After optimization:
HomePage: 15ms (filtered cached with useMemo)
PropertyCard × 100: memoized, skips re-render
Total: 15ms 🟢

16x faster! ⚡

Common Performance Pitfalls

Pitfall 1: Unnecessary State

// ❌ BAD - derived state
const [items, setItems] = useState([]);
const [count, setCount] = useState(0);

useEffect(() => {
   setCount(items.length); // Unnecessary state
}, [items]);

// ✅ GOOD - calculate on demand
const [items, setItems] = useState([]);
const count = items.length; // Just calculate it!

Pitfall 2: Inline Objects/Arrays

// ❌ BAD - new object every render
<Component style={{ color: 'red' }} />

// ✅ GOOD - stable reference
const style = { color: 'red' };
<Component style={style} />

// Or use useMemo for dynamic values
const style = useMemo(() => ({
   color: theme === 'dark' ? 'white' : 'black'
}), [theme]);

Pitfall 3: Large Lists Without Keys

// ❌ BAD - no keys, React re-renders everything
{items.map(item => <Card>{item.name}</Card>)}

// ✅ GOOD - stable keys, React only updates changed items
{items.map(item => <Card key={item.id}>{item.name}</Card>)}

Before vs After This Module

Before Module 5:

function HomePage() {
   const [listings, setListings] = useState([]);
   const [loading, setLoading] = useState(true);
   const [filters, setFilters] = useState({});
   
   // Repeated fetch logic
   useEffect(() => {
      fetch('/api/listings')
         .then(res => res.json())
         .then(data => {
            setListings(data);
            setLoading(false);
         });
   }, []);
   
   // Re-filters every render (slow!)
   const filtered = listings.filter(item =>
      item.price < filters.maxPrice
   );
   
   return <PropertyList properties={filtered} />;
}

After Module 5:

function HomePage() {
   // Reusable custom hook
   const { data: listings, loading } = useFetch('/api/listings');
   const [filters, setFilters] = useState({});
   
   // Cached filtering (only recalculates when needed)
   const filtered = useMemo(() =>
      listings?.filter(item =>
         item.price < filters.maxPrice
      ) ?? [],
      [listings, filters]
   );
   
   return <PropertyList properties={filtered} />;
}

// Memoized component (skips re-renders)
const PropertyList = React.memo(function PropertyList({ properties }) {
   return properties.map(p => <PropertyCard key={p.id} property={p} />);
});

Results:

  • ✅ Less code duplication
  • ✅ Better performance
  • ✅ Easier to maintain
  • ✅ Reusable logic

Time Estimate

⏱️ 3-4 hours to complete all 10 lessons

  • Lessons 1-3 (Custom hooks): ~1.5 hours
  • Lessons 4-5 (useMemo): ~45 minutes
  • Lessons 6-7 (useCallback & React.memo): ~45 minutes
  • Lessons 8-10 (Profiling & review): ~1 hour

What Makes This Module Important

Professional React developers must:

  • Write reusable, maintainable code
  • Optimize performance for production
  • Use profiling tools to measure impact
  • Know when and how to optimize

After this module, you'll:

  • Extract custom hooks confidently
  • Optimize intelligently (not prematurely)
  • Profile and debug performance issues
  • Write production-quality React code

Let's Begin!

Ready to level up your React skills with custom hooks and performance optimization? Let's start by creating a reusable useFetch hook!