Code To Learn logo

Code To Learn

M5: Hooks & Performance

L10: M5 Review

Review all optimization patterns and best practices

Let's review everything you've learned about React performance optimization and advanced patterns!

What You've Learned

Custom Hooks (Lessons 1-3)

Created useFetch hook to eliminate duplicate code:

// Before: 30+ lines in every component
const [data, setData] = useState();
const [isLoading, setIsLoading] = useState(true);
useEffect(() => { /* ... */ }, []);

// After: 1 line
const { data, isLoading, error } = useFetch('/api/endpoint');

Benefits:

  • 50% less code
  • Consistent error handling
  • Automatic cleanup
  • Reusable across app

useMemo (Lessons 4-5)

Optimized expensive calculations:

// Memoize filtered listings
const filteredListings = useMemo(() => {
  return listings.filter(listing => {
    // Complex filtering logic
  });
}, [listings, search, dates, guests]);

Benefits:

  • Only recalculates when dependencies change
  • 85% faster filtering
  • No wasted CPU cycles

useCallback (Lesson 6)

Memoized callback functions:

// Stable function reference
const handleSearch = useCallback((value) => {
  setSearch(value);
}, []);

// Pass to memoized children
<MemoizedFilters onSearch={handleSearch} />

Benefits:

  • Prevents unnecessary re-renders
  • Works with React.memo
  • Stable function references

React.memo (Lesson 7)

Prevented component re-renders:

// Only re-renders when props change
const PropertyCard = React.memo(function PropertyCard({ listing }) {
  return <div>{listing.title}</div>;
});

Benefits:

  • 80% fewer renders
  • Smoother scrolling
  • Better performance

Performance Profiling (Lesson 8)

Measured optimization impact:

  • Used React DevTools Profiler
  • Identified slow components
  • Measured before/after
  • Achieved 73% improvement

Caching (Lesson 9)

Eliminated duplicate API requests:

// Cached responses in memory
const { data } = useFetch('/listings');  // First call: fetch
const { data } = useFetch('/listings');  // Second call: cached!

Benefits:

  • 50% faster navigation
  • 50% fewer API requests
  • Better user experience

Complete Optimized Example

Here's how all optimizations work together:

src/pages/HomePage.jsx
import React, { useState, useMemo, useCallback } from 'react';
import { useFetch } from '@/hooks/useFetch';
import ListingList from '@/components/ListingList';
import ListingFilters from '@/components/ListingFilters';

export function HomePage() {
  // 1. Custom hook - eliminates boilerplate
  const { data: listings, isLoading, error } = useFetch('/listings');
  
  const [search, setSearch] = useState('');
  const [dates, setDates] = useState({ from: null, to: null });
  const [guests, setGuests] = useState(1);
  
  // 2. useCallback - stable function references
  const handleSearchChange = useCallback((value) => {
    setSearch(value);
  }, []);
  
  const handleDatesChange = useCallback((newDates) => {
    setDates(newDates);
  }, []);
  
  const handleGuestsChange = useCallback((value) => {
    setGuests(value);
  }, []);
  
  // 3. useMemo - optimize filtering
  const filteredListings = useMemo(() => {
    if (!listings) return [];
    
    return listings.filter(listing => {
      const matchesSearch = listing.title
        .toLowerCase()
        .includes(search.toLowerCase());
      
      const matchesGuests = listing.maxGuests >= guests;
      
      const matchesDates = dates.from && dates.to
        ? isAvailable(listing, dates.from, dates.to)
        : true;
      
      return matchesSearch && matchesGuests && matchesDates;
    });
  }, [listings, search, dates, guests]);
  
  if (isLoading) {
    return (
      <div className="flex justify-center items-center min-h-screen">
        <Spinner />
      </div>
    );
  }
  
  if (error) {
    return (
      <div className="container mx-auto px-4 py-8">
        <ErrorMessage message={error} />
      </div>
    );
  }
  
  return (
    <div className="container mx-auto px-4 py-8">
      {/* 4. React.memo - prevents unnecessary renders */}
      <ListingFilters
        search={search}
        onSearchChange={handleSearchChange}
        dates={dates}
        onDatesChange={handleDatesChange}
        guests={guests}
        onGuestsChange={handleGuestsChange}
      />
      <ListingList listings={filteredListings} />
    </div>
  );
}

function isAvailable(listing, from, to) {
  // Date availability logic
  return true;
}
src/components/ListingList.jsx
import React from 'react';
import { PropertyCard } from './PropertyCard';

// Memoized - doesn't re-render unless listings change
const ListingList = React.memo(function ListingList({ listings }) {
  if (listings.length === 0) {
    return (
      <div className="text-center py-8 text-gray-500">
        No listings found
      </div>
    );
  }
  
  return (
    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
      {listings.map(listing => (
        <PropertyCard key={listing.id} listing={listing} />
      ))}
    </div>
  );
});

export default ListingList;
src/components/PropertyCard.jsx
import React from 'react';
import { Link } from 'react-router-dom';

// Memoized - only re-renders when listing prop changes
const PropertyCard = React.memo(function PropertyCard({ listing }) {
  return (
    <Link 
      to={`/listings/${listing.id}`}
      className="block border rounded-lg overflow-hidden hover:shadow-lg transition"
    >
      <img 
        src={listing.images[0]} 
        alt={listing.title}
        className="w-full h-48 object-cover"
      />
      <div className="p-4">
        <h3 className="font-semibold text-lg">{listing.title}</h3>
        <p className="text-gray-600">{listing.location}</p>
        <div className="mt-2 flex justify-between items-center">
          <span className="text-xl font-bold">${listing.price}/night</span>
          <span className="text-sm text-gray-500">⭐ {listing.rating}</span>
        </div>
      </div>
    </Link>
  );
});

export default PropertyCard;
src/hooks/useFetch.js
import { useState, useEffect } from 'react';
import api from '@/api';

// Cache outside hook - shared across all components
const cache = {};
const CACHE_DURATION = 5 * 60 * 1000;

export function useFetch(url, options = {}) {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);
  
  const {
    cacheTime = CACHE_DURATION,
    enableCache = true
  } = options;
  
  useEffect(() => {
    const controller = new AbortController();
    
    const fetchData = async () => {
      // Check cache
      if (enableCache && cache[url]) {
        const cached = cache[url];
        const age = Date.now() - cached.timestamp;
        
        if (age < cacheTime) {
          setData(cached.data);
          setIsLoading(false);
          return;
        }
      }
      
      // Fetch from API
      try {
        setIsLoading(true);
        setError(null);
        
        const response = await api.get(url, {
          signal: controller.signal
        });
        
        // Update cache
        if (enableCache) {
          cache[url] = {
            data: response.data,
            timestamp: Date.now()
          };
        }
        
        setData(response.data);
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err.message);
        }
      } finally {
        setIsLoading(false);
      }
    };
    
    fetchData();
    
    return () => controller.abort();
  }, [url, cacheTime, enableCache]);
  
  return { data, isLoading, error };
}

export function clearCache(url) {
  if (url) {
    delete cache[url];
  } else {
    Object.keys(cache).forEach(key => delete cache[key]);
  }
}

Optimization Checklist

Use this checklist for optimizing React apps:

Performance Targets

MetricTargetGoodNeeds Work
Render time< 16ms< 10ms> 20ms
Time to Interactive< 1s< 500ms> 2s
First Contentful Paint< 1.8s< 1s> 3s
Lighthouse Score> 90> 95< 80
Cache hit rate> 70%> 80%< 50%
API requestsMinimalCachedDuplicates

Common Patterns

// Optimized data fetching pattern
function DataPage() {
  // 1. Use custom hook with caching
  const { data, isLoading, error } = useFetch('/api/data');
  
  // 2. Memoize derived data
  const processedData = useMemo(() => {
    return data?.map(item => ({
      ...item,
      computed: expensiveComputation(item)
    }));
  }, [data]);
  
  // 3. Memoize callbacks
  const handleRefresh = useCallback(() => {
    clearCache('/api/data');
    window.location.reload();
  }, []);
  
  if (isLoading) return <Spinner />;
  if (error) return <Error message={error} />;
  
  // 4. Pass to memoized component
  return <MemoizedDataDisplay data={processedData} onRefresh={handleRefresh} />;
}
// Optimized list rendering
function ListPage({ items, onItemClick }) {
  // 1. Memoize filtered/sorted items
  const displayedItems = useMemo(() => {
    return items
      .filter(item => item.active)
      .sort((a, b) => b.date - a.date);
  }, [items]);
  
  // 2. Memoize item callback
  const handleClick = useCallback((id) => {
    onItemClick(id);
  }, [onItemClick]);
  
  return (
    <div>
      {displayedItems.map(item => (
        <MemoizedListItem
          key={item.id}
          item={item}
          onClick={handleClick}
        />
      ))}
    </div>
  );
}

// 3. Memoize list item
const MemoizedListItem = React.memo(ListItem);
// Optimized form handling
function FormPage() {
  const [formData, setFormData] = useState({
    name: '',
    email: ''
  });
  
  // 1. Memoize field change handler
  const handleFieldChange = useCallback((field, value) => {
    setFormData(prev => ({
      ...prev,
      [field]: value
    }));
  }, []);
  
  // 2. Memoize submit handler
  const handleSubmit = useCallback(async (e) => {
    e.preventDefault();
    await api.post('/submit', formData);
  }, [formData]);
  
  return (
    <form onSubmit={handleSubmit}>
      <MemoizedInput
        value={formData.name}
        onChange={(v) => handleFieldChange('name', v)}
      />
      <MemoizedInput
        value={formData.email}
        onChange={(v) => handleFieldChange('email', v)}
      />
    </form>
  );
}
// Optimized context usage
function AppProvider({ children }) {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  
  // 1. Memoize context value
  const value = useMemo(() => ({
    user,
    theme,
    setUser,
    setTheme
  }), [user, theme]);
  
  return (
    <AppContext.Provider value={value}>
      {children}
    </AppContext.Provider>
  );
}

// 2. Split contexts for granular updates
function UserProvider({ children }) {
  const [user, setUser] = useState(null);
  const value = useMemo(() => ({ user, setUser }), [user]);
  return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}

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

Before & After Metrics

AspectBeforeAfterImprovement
Code lines~200 lines~100 lines50% reduction
HomePage render45ms12ms73% faster
Typing latency50ms10ms80% faster
API requests4 per page2 per page50% fewer
Cache hit rate0%78%New capability
Bundle sizeSameSameNo increase

Total improvement: 3-4x faster perceived performance! 🚀

Next Steps

Now that you've mastered performance optimization, you're ready for:

  1. Module 6: State Management - Global state with Redux Toolkit
  2. Module 7: Forms & Auth - React Hook Form and authentication
  3. Module 8: Deployment - Ship your app to production!

Key Takeaways

Measure First, Optimize Second

Don't guess what's slow - use React DevTools Profiler to identify actual bottlenecks.

Optimize What Matters

Focus on:

  • Expensive operations (> 5ms)
  • Frequent renders (many times per second)
  • User-facing interactions (typing, scrolling, clicking)

Use the Right Tool

  • Custom hooks - Reusable logic
  • useMemo - Expensive calculations
  • useCallback - Stable function references
  • React.memo - Component memoization
  • Caching - Eliminate duplicate work

Don't Over-Optimize

Premature optimization adds complexity. Only optimize when you have:

  • Measured performance problem
  • Profiler evidence
  • Slow user experience

Congratulations! 🎉

You've completed Module 5 and learned:

  • ✅ Custom hooks for code reuse
  • ✅ useMemo for expensive calculations
  • ✅ useCallback for stable functions
  • ✅ React.memo for component optimization
  • ✅ Performance profiling techniques
  • ✅ In-memory caching strategies

You're now a React performance expert! Your apps will be fast, efficient, and provide excellent user experiences. 🚀

Keep these patterns in mind as you build, and remember: measure, optimize, measure again!