Advanced React Hooks Patterns
Master advanced React Hooks patterns including custom hooks, useReducer, useContext, and performance optimization techniques.
Tutorial Info
Prerequisites
- Solid understanding of React basics
- Experience with useState and useEffect
- JavaScript ES6+ knowledge
- TypeScript familiarity
What You'll Learn
- Create powerful custom hooks
- Master useReducer for complex state
- Implement Context API effectively
- Optimize component performance
- Apply advanced patterns in real projects
Advanced React Hooks Patterns
In this comprehensive guide, we'll explore advanced React Hooks patterns that will take your React skills to the next level. We'll cover custom hooks, useReducer, useContext, and performance optimization techniques.
Custom Hooks
Custom hooks are one of the most powerful features of React Hooks. They allow you to extract component logic into reusable functions.
Creating a useLocalStorage Hook
import { useState, useEffect } from 'react'; function useLocalStorage<T>(key: string, initialValue: T) { // Get from local storage then parse stored json or return initialValue const [storedValue, setStoredValue] = useState<T>(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { console.log(error); return initialValue; } }); // Return a wrapped version of useState's setter function that persists the new value to localStorage const setValue = (value: T | ((val: T) => T)) => { try { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); window.localStorage.setItem(key, JSON.stringify(valueToStore)); } catch (error) { console.log(error); } }; return [storedValue, setValue] as const; }
Using the Custom Hook
function Profile() { const [name, setName] = useLocalStorage('name', ''); const [email, setEmail] = useLocalStorage('email', ''); return ( <form> <input type="text" placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} /> <input type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} /> </form> ); }
useReducer for Complex State
When state logic is complex, useReducer can be more suitable than useState.
import React, { useReducer } from 'react'; interface State { count: number; history: number[]; } type Action = | { type: 'increment' } | { type: 'decrement' } | { type: 'reset' } | { type: 'set'; payload: number }; function reducer(state: State, action: Action): State { switch (action.type) { case 'increment': return { count: state.count + 1, history: [...state.history, state.count + 1] }; case 'decrement': return { count: state.count - 1, history: [...state.history, state.count - 1] }; case 'reset': return { count: 0, history: [0] }; case 'set': return { count: action.payload, history: [...state.history, action.payload] }; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, { count: 0, history: [0] }); return ( <div> <p>Count: {state.count}</p> <p>History: {state.history.join(', ')}</p> <button onClick={() => dispatch({ type: 'increment' })}>+</button> <button onClick={() => dispatch({ type: 'decrement' })}>-</button> <button onClick={() => dispatch({ type: 'reset' })}>Reset</button> </div> ); }
Context and useContext
Context provides a way to pass data through the component tree without prop drilling.
import React, { createContext, useContext, useReducer, ReactNode } from 'react'; // Theme Context interface ThemeContextType { theme: string; toggleTheme: () => void; } const ThemeContext = createContext<ThemeContextType | undefined>(undefined); function themeReducer(state: { theme: string }, action: { type: string }) { switch (action.type) { case 'TOGGLE_THEME': return { theme: state.theme === 'light' ? 'dark' : 'light' }; default: return state; } } export function ThemeProvider({ children }: { children: ReactNode }) { const [state, dispatch] = useReducer(themeReducer, { theme: 'light' }); const toggleTheme = () => dispatch({ type: 'TOGGLE_THEME' }); return ( <ThemeContext.Provider value={{ theme: state.theme, toggleTheme }}> {children} </ThemeContext.Provider> ); } export function useTheme() { const context = useContext(ThemeContext); if (context === undefined) { throw new Error('useTheme must be used within a ThemeProvider'); } return context; }
Performance Optimization with useMemo and useCallback
import React, { useState, useMemo, useCallback } from 'react'; interface Item { id: number; name: string; category: string; } function ExpensiveList({ items }: { items: Item[] }) { const [filter, setFilter] = useState(''); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); // Memoize expensive computation const filteredAndSortedItems = useMemo(() => { console.log('Computing filtered and sorted items'); let filtered = items.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()) ); return filtered.sort((a, b) => { if (sortOrder === 'asc') { return a.name.localeCompare(b.name); } return b.name.localeCompare(a.name); }); }, [items, filter, sortOrder]); // Memoize callback to prevent unnecessary re-renders const handleItemClick = useCallback((id: number) => { console.log(`Clicked item ${id}`); }, []); return ( <div> <input type="text" placeholder="Filter items..." value={filter} onChange={(e) => setFilter(e.target.value)} /> <button onClick={() => setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')}> Sort {sortOrder === 'asc' ? 'Descending' : 'Ascending'} </button> <ul> {filteredAndSortedItems.map(item => ( <li key={item.id} onClick={() => handleItemClick(item.id)}> {item.name} </li> ))} </ul> </div> ); }
Best Practices
- Keep hooks at the top level: Never call hooks inside loops, conditions, or nested functions
- Use custom hooks for reusable logic: Extract complex stateful logic into custom hooks
- Optimize with useMemo and useCallback: But don't overuse them - measure first
- Use useReducer for complex state: When state has multiple sub-values or complex update logic
- Provide meaningful names: Custom hooks should start with "use" and be descriptive
Conclusion
Advanced React Hooks patterns enable you to write cleaner, more maintainable React code. By mastering custom hooks, useReducer, context, and performance optimization techniques, you can build scalable React applications with confidence.