Master React Hooks with a comprehensive guide to useState, useEffect, useContext, useReducer, useMemo, useCallback, and custom hooks.

Francis Njenga
Lead Developer
React Hooks, introduced in React 16.8, revolutionized how developers write and organize React components. They allow you to use state and other React features without writing classes.
Before Hooks, React developers had to choose between function components (simpler but limited) and class components (more powerful but complex). Hooks bring the power of class components to function components.
Manages local component state
Handles side effects
Accesses context values
Manages complex state logic
One of the most powerful features of Hooks is the ability to create your own custom hooks that encapsulate reusable logic.
React Hooks represent a significant step forward in React's evolution, making it easier to write and maintain components while encouraging better patterns for code organization and reuse.
By mastering hooks, you can write more concise, maintainable, and powerful React applications that are easier to test and reason about.
function Counter() { const [count, setCount] = useState(0); const [isActive, setIsActive] = useState(false); const [user, setUser] = useState({ name: '', email: '' }); const updateEmail = (newEmail) => { setUser({ ...user, email: newEmail }); }; return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> <p>Status: {isActive ? 'Active' : 'Inactive'}</p> <button onClick={() => setIsActive(!isActive)}>Toggle</button> <div> <input value={user.name} onChange={(e) => setUser({...user, name: e.target.value})} placeholder="Name" /> <input value={user.email} onChange={(e) => updateEmail(e.target.value)} placeholder="Email" /> </div> </div> ); }
Using useState to manage various state types
function DataFetcher() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [userId, setUserId] = useState(1); useEffect(() => { setLoading(true); setError(null); async function fetchData() { try { const response = await fetch( `https://jsonplaceholder.typicode.com/users/${userId}` ); const userData = await response.json(); setData(userData); } catch (err) { setError(err.message); } finally { setLoading(false); } } fetchData(); return () => { console.log('Cleanup before fetching new data'); }; }, [userId]); return ( <div> <button onClick={() => setUserId(id => Math.max(1, id - 1))}> Previous User </button> <span> User ID: {userId} </span> <button onClick={() => setUserId(id => id + 1)}> Next User </button> {loading && <p>Loading...</p>} {error && <p>Error: {error}</p>} {data && ( <div> <h3>{data.name}</h3> <p>Email: {data.email}</p> </div> )} </div> ); }
Data fetching with useEffect and cleanup
const ThemeContext = createContext('light'); function ThemedButton() { const theme = useContext(ThemeContext); return ( <button style={{ background: theme === 'dark' ? '#333' : '#fff', color: theme === 'dark' ? '#fff' : '#333' }}> I'm styled by context! </button> ); } function App() { return ( <ThemeContext.Provider value="dark"> <ThemedButton /> </ThemeContext.Provider> ); }
Using context in function components
function counterReducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; case 'reset': return { count: 0 }; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(counterReducer, { count: 0 }); 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> ); }
Redux-like state management with useReducer
function ExpensiveComponent({ a, b }) { const result = useMemo(() => { console.log('Calculating...'); return a * b; }, [a, b]); const handleClick = useCallback(() => { console.log('Result:', result); }, [result]); return ( <div> <p>Result: {result}</p> <button onClick={handleClick}>Log Result</button> </div> ); }
Optimizing performance with memoization
function TextInput() { const inputRef = useRef(null); const [value, setValue] = useState(''); const prevValue = useRef(''); useEffect(() => { prevValue.current = value; }, [value]); const focusInput = () => { inputRef.current.focus(); }; return ( <div> <input ref={inputRef} value={value} onChange={(e) => setValue(e.target.value)} /> <button onClick={focusInput}>Focus Input</button> <p>Current: {value}, Previous: {prevValue.current}</p> </div> ); }
Using useRef for DOM access and value persistence
function useLocalStorage(key, initialValue) { const [storedValue, setStoredValue] = useState(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (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); } }; return [storedValue, setValue]; } function LoginForm() { const [username, setUsername] = useLocalStorage('username', ''); const [password, setPassword] = useLocalStorage('password', ''); return ( <form> <input value={username} onChange={(e) => setUsername(e.target.value)} placeholder="Username" /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" /> </form> ); }
Creating and using a custom localStorage hook

Lead Developer
Francis Njenga is an experienced Lead Developer specializing in React, Next.js, and modern JavaScript frameworks, with a strong background in web development.