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

Francis Njenga
Lead Developer
A Deep Dive into React Hooks
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.
Key Benefits of Hooks
- Reuse stateful logic between components
- Organize related code together
- Simplify complex components
- Use React features without classes
- Better performance optimization
Why Hooks Matter
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.
Core React Hooks
useState
Manages local component state
useEffect
Handles side effects
useContext
Accesses context values
useReducer
Manages complex state logic
Performance Optimization Hooks
- useMemo: Memoizes expensive calculations
- useCallback: Memoizes callback functions
- useRef: Persists values across renders
Custom Hooks
One of the most powerful features of Hooks is the ability to create your own custom hooks that encapsulate reusable logic.
Rules of Hooks
Essential Rules to Follow
- Only call hooks at the top level
- Only call hooks from React functions
- Name custom hooks starting with "use"
Best Practices
- Keep custom hooks focused on single responsibilities
- Specify all dependencies in dependency arrays
- Extract complex logic into custom hooks
- Use the React DevTools profiler for optimization
Conclusion
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

Francis Njenga
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.