Mastering React Hooks
A comprehensive guide to React Hooks. Learn useState, useEffect, useContext, and custom hooks with practical examples.
Mastering React Hooks
React Hooks have revolutionized how we write functional components. Let's dive deep into the most important hooks and learn how to use them effectively.
What are Hooks?
Hooks are functions that allow you to "hook into" React state and lifecycle features from function components. They were introduced in React 16.8.
useState Hook
The most basic hook for managing state:
import { useState } from 'react' function Counter() { const [count, setCount] = useState(0) return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}> Increment </button> </div> ) }
Best Practices
- Use multiple useState calls for unrelated state
- Use objects for related state
- Always use the setter function to update state
useEffect Hook
For side effects in functional components:
import { useEffect, useState } from 'react' function UserProfile({ userId }) { const [user, setUser] = useState(null) useEffect(() => { // Fetch user data fetchUser(userId).then(setUser) }, [userId]) // Dependency array return <div>{user?.name}</div> }
Cleanup Function
useEffect(() => { const subscription = subscribe() return () => { subscription.unsubscribe() // Cleanup } }, [])
useContext Hook
For consuming context without nesting:
import { createContext, useContext } from 'react' const ThemeContext = createContext() function ThemedButton() { const theme = useContext(ThemeContext) return <button className={theme}>Click me</button> }
Custom Hooks
Create reusable logic:
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 { setStoredValue(value) window.localStorage.setItem(key, JSON.stringify(value)) } catch (error) { console.log(error) } } return [storedValue, setValue] }
useReducer Hook
For complex state logic:
function todoReducer(state, action) { switch (action.type) { case 'ADD_TODO': return [...state, action.payload] case 'REMOVE_TODO': return state.filter(todo => todo.id !== action.payload) default: return state } } function TodoList() { const [todos, dispatch] = useReducer(todoReducer, []) return ( <div> {todos.map(todo => ( <div key={todo.id}> {todo.text} <button onClick={() => dispatch({ type: 'REMOVE_TODO', payload: todo.id })}> Remove </button> </div> ))} </div> ) }
useMemo and useCallback
For performance optimization:
import { useMemo, useCallback } from 'react' function ExpensiveComponent({ items }) { // Memoize expensive calculation const expensiveValue = useMemo(() => { return items.reduce((acc, item) => acc + item.value, 0) }, [items]) // Memoize callback function const handleClick = useCallback(() => { console.log('Button clicked') }, []) return ( <div> <p>Total: {expensiveValue}</p> <button onClick={handleClick}>Click me</button> </div> ) }
Rules of Hooks
- Only call hooks at the top level
- Only call hooks from React functions
- Hook names must start with "use"
Common Mistakes
❌ Don't call hooks conditionally
// Wrong if (condition) { useEffect(() => {}) }
✅ Do call hooks unconditionally
// Correct useEffect(() => { if (condition) { // Do something } })
Testing Hooks
Use React Testing Library:
import { renderHook, act } from '@testing-library/react' test('useCounter', () => { const { result } = renderHook(() => useCounter()) act(() => { result.current.increment() }) expect(result.current.count).toBe(1) })
Conclusion
Hooks make React components more functional and easier to test. Start with useState and useEffect, then gradually explore more advanced hooks as needed.
Remember: Hooks are powerful, but with great power comes great responsibility. Use them wisely!