Mastering React Hooks: Advanced Patterns and Best Practices
React Hooks have revolutionized how we write React components, making functional components more powerful and eliminating the need for class components in most cases.
Understanding the Hook Rules
Before diving into advanced patterns, it's crucial to understand the fundamental rules:
1. Only call Hooks at the top level - Never inside loops, conditions, or nested functions
2. Only call Hooks from React functions - Either React components or custom Hooks
## Advanced Hook Patterns
Custom Hooks for Data Fetching
``jsx
function useApi(url) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false))
}, [url])
return { data, loading, error }
}
`
### useReducer for Complex State
When state logic becomes complex, useReducer is often better than useState:
`jsx
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, { id: Date.now(), text: action.text, done: false }]
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id ? { ...todo, done: !todo.done } : todo
)
default:
return state
}
}
function TodoApp() {
const [todos, dispatch] = useReducer(todoReducer, [])
return (
// Your component JSX
)
}
`
## Performance Optimization
### useMemo and useCallback
Use these hooks to prevent unnecessary re-renders:
`jsx
function ExpensiveComponent({ items, filter }) {
const filteredItems = useMemo(() =>
items.filter(item => item.category === filter),
[items, filter]
)
const handleClick = useCallback((id) => {
// Handle click logic
}, [])
return (
// Component JSX
)
}
``
## Conclusion
Mastering React Hooks takes practice, but the patterns shown here will help you write more efficient and maintainable React applications.
Before diving into advanced patterns, it's crucial to understand the fundamental rules:
1. Only call Hooks at the top level - Never inside loops, conditions, or nested functions
2. Only call Hooks from React functions - Either React components or custom Hooks
## Advanced Hook Patterns
Custom Hooks for Data Fetching
``jsx
function useApi(url) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false))
}, [url])
return { data, loading, error }
}
`
### useReducer for Complex State
When state logic becomes complex, useReducer is often better than useState:
`jsx
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, { id: Date.now(), text: action.text, done: false }]
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id ? { ...todo, done: !todo.done } : todo
)
default:
return state
}
}
function TodoApp() {
const [todos, dispatch] = useReducer(todoReducer, [])
return (
// Your component JSX
)
}
`
## Performance Optimization
### useMemo and useCallback
Use these hooks to prevent unnecessary re-renders:
`jsx
function ExpensiveComponent({ items, filter }) {
const filteredItems = useMemo(() =>
items.filter(item => item.category === filter),
[items, filter]
)
const handleClick = useCallback((id) => {
// Handle click logic
}, [])
return (
// Component JSX
)
}
``
## Conclusion
Mastering React Hooks takes practice, but the patterns shown here will help you write more efficient and maintainable React applications.
function useApi(url) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false))
}, [url])
return { data, loading, error }
}
### useReducer for Complex State
When state logic becomes complex, useReducer is often better than useState:
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, { id: Date.now(), text: action.text, done: false }]
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id ? { ...todo, done: !todo.done } : todo
)
default:
return state
}
}
function TodoApp() {
const [todos, dispatch] = useReducer(todoReducer, [])
return (
// Your component JSX
)
}
## Performance Optimization
### useMemo and useCallback
Use these hooks to prevent unnecessary re-renders:
function ExpensiveComponent({ items, filter }) {
const filteredItems = useMemo(() =>
items.filter(item => item.category === filter),
[items, filter]
)
const handleClick = useCallback((id) => {
// Handle click logic
}, [])
return (
// Component JSX
)
}
