React hooks revolutionized how we write components. Understanding advanced patterns and best practices is essential for building scalable React applications.
// Functional updates for complex state
const [state, setState] = useState({ count: 0, name: '' });
// Good: Functional update
const incrementCount = () => {
setState(prev => ({ ...prev, count: prev.count + 1 }));
};
// Lazy initial state for expensive computations
const [data, setData] = useState(() => {
return expensiveComputation();
});
// Cleanup subscriptions
useEffect(() => {
const subscription = subscribeToData((data) => {
setData(data);
});
return () => {
subscription.unsubscribe();
};
}, []);
// Conditional effects
useEffect(() => {
if (shouldFetch) {
fetchData();
}
}, [shouldFetch, userId]);
function useApi(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
if (!response.ok) throw new Error('API call failed');
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
// Usage
function UserProfile({ userId }) {
const { data: user, loading, error } = useApi(`/api/users/${userId}`);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{user.name}</div>;
}
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(`Error reading localStorage key "${key}":`, error);
return initialValue;
}
});
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(`Error setting localStorage key "${key}":`, error);
}
};
return [storedValue, setValue];
}
const initialState = { count: 0, loading: false, error: null };
function counterReducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'SET_LOADING':
return { ...state, loading: action.payload };
case 'SET_ERROR':
return { ...state, error: action.payload };
default:
throw new Error(`Unknown action type: ${action.type}`);
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
</div>
);
}
function ExpensiveList({ items, filter }) {
// Memoize expensive calculations
const filteredItems = useMemo(() => {
return items.filter(item => item.category === filter);
}, [items, filter]);
// Memoize event handlers
const handleItemClick = useCallback((itemId) => {
// Handle click
}, []);
return (
<div>
{filteredItems.map(item => (
<Item
key={item.id}
item={item}
onClick={handleItemClick}
/>
))}
</div>
);
}
// Context for global state
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = useCallback(() => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
}, []);
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// Custom hook for theme
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
This builds on javascript/fundamentals and connects to state-management patterns.
For performance optimization, see performance-tips.
Component patterns are covered in component-patterns.
#react #hooks #state #patterns