2024-09-01
Master React Hooks from useState to custom hooks, including performance optimization and best practices.
React Hooks revolutionized functional components by enabling state and lifecycle features. This comprehensive guide covers all built-in hooks, custom hook patterns, performance optimization, and common pitfalls to avoid.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
// Functional update for complex state
const increment = () => setCount(prev => prev + 1);
// Lazy initialization for expensive computation
const [expensiveState] = useState(() => {
return computeExpensiveValue();
});
return (
<button onClick={increment}>
Count: {count}
</button>
);
}
import { useEffect, useState } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Cleanup flag to prevent state updates after unmount
let cancelled = false;
async function fetchUser() {
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
if (!cancelled) {
setUser(data);
setLoading(false);
}
} catch (error) {
if (!cancelled) {
setLoading(false);
}
}
}
fetchUser();
// Cleanup function
return () => {
cancelled = true;
};
}, [userId]); // Dependency array
return loading ? <div>Loading...</div> : <div>{user?.name}</div>;
}
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
import { useMemo } from 'react';
function ExpensiveList({ items, filter }) {
// Memoize expensive filtering operation
const filteredItems = useMemo(() => {
console.log('Filtering items...');
return items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}, [items, filter]); // Only recompute when dependencies change
return (
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
import { useCallback, useState, memo } from 'react';
// Child component with React.memo
const ExpensiveChild = memo(({ onClick }) => {
console.log('ExpensiveChild rendered');
return <button onClick={onClick}>Click me</button>;
});
function Parent() {
const [count, setCount] = useState(0);
// Without useCallback, this creates new function every render
// const handleClick = () => console.log('Clicked');
// With useCallback, function is memoized
const handleClick = useCallback(() => {
console.log('Clicked');
}, []); // Empty dependencies = never recreated
return (
<div>
<ExpensiveChild onClick={handleClick} />
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
</div>
);
}
import { useReducer } from 'react';
const initialState = { count: 0, history: [] };
function reducer(state, action) {
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 initialState;
default:
throw new Error(`Unknown action: ${action.type}`);
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}
import { useRef, useEffect } from 'react';
function TextInput() {
// DOM reference
const inputRef = useRef(null);
// Mutable value that persists across renders
const renderCount = useRef(0);
useEffect(() => {
renderCount.current += 1;
console.log(`Rendered ${renderCount.current} times`);
});
const focusInput = () => {
inputRef.current?.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
Create reusable logic with custom hooks:
// Custom hook for local storage
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);
return initialValue;
}
});
const setValue = (value) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// Custom hook for fetch
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortController = new AbortController();
fetch(url, { signal: abortController.signal })
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
return () => abortController.abort();
}, [url]);
return { data, loading, error };
}
Dependency Arrays
Always include all dependencies in useEffect, useMemo, and useCallback arrays to avoid bugs.
Custom Hook Naming
Always start custom hook names with "use" to enable React's linting rules.
Avoid Premature Optimization
Don't use useMemo and useCallback everywhere. Profile first, optimize second.
Published on 2024-09-01 • Category: Frontend
← Back to BlogFree online developer tools and utilities for encoding, formatting, generating, and analyzing data. No registration required - all tools work directly in your browser.
Built for developers, by developers. Privacy-focused and open source.
Free online tools for Base64 encoding, JSON formatting, URL encoding, hash generation, UUID creation, QR codes, JWT decoding, timestamp conversion, regex testing, and more.
© 2024 NarvikHub. All rights reserved.