
Understanding React Hooks
React Hooks revolutionized how we write React components. Instead of class components with lifecycle methods, we now have functional components with hooks that are simpler and more intuitive.
Why Hooks?
Before hooks, functional components were stateless. If you needed state or lifecycle methods, you had to convert your component to a class. Hooks change that entirely, allowing you to use state and other React features without writing a class.
useState
The most basic hook is useState. It lets you add state to functional components:
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>
)
}
The useState hook returns an array with two elements: the current state value and a function to update it. You can call useState multiple times in a component to manage different pieces of state.
useEffect
Handle side effects in your components with useEffect:
import { useState, useEffect } from 'react'
function DocumentTitle() {
const [count, setCount] = useState(0)
useEffect(() => {
document.title = `Count: ${count}`
}, [count])
return <button onClick={() => setCount(count + 1)}>Increment</button>
}
The useEffect hook takes two arguments: a function containing the side effect code, and a dependency array. The effect runs after every render where one of the dependencies has changed.
useContext
Share data between components without prop drilling:
import { createContext, useContext } from 'react'
const ThemeContext = createContext('light')
function ThemedButton() {
const theme = useContext(ThemeContext)
return <button className={theme}>Themed Button</button>
}
Custom Hooks
One of the most powerful features of hooks is the ability to create your own:
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key)
return stored ? JSON.parse(stored) : initialValue
})
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value))
}, [key, value])
return [value, setValue]
}
// Usage
function App() {
const [name, setName] = useLocalStorage('name', 'Guest')
return <input value={name} onChange={(e) => setName(e.target.value)} />
}
Best Practices
- Only call hooks at the top level - Don't call hooks inside loops, conditions, or nested functions
- Only call hooks from React functions - Call them from React function components or custom hooks
- Use ESLint plugin for hooks rules - Install
eslint-plugin-react-hooksto catch violations - Keep effects focused - Each
useEffectshould handle one concern - Clean up effects - Return a cleanup function from
useEffectwhen needed
Example: Proper Cleanup
function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(roomId)
connection.connect()
// Cleanup function
return () => {
connection.disconnect()
}
}, [roomId])
return <div>Connected to {roomId}</div>
}
Common Pitfalls
Stale Closures
Be careful with closures in effects:
// ❌ Bad: stale closure
function Counter() {
const [count, setCount] = useState(0)
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1) // Always uses initial count value
}, 1000)
return () => clearInterval(interval)
}, [])
return <div>{count}</div>
}
// ✅ Good: use functional update
function Counter() {
const [count, setCount] = useState(0)
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1) // Uses latest value
}, 1000)
return () => clearInterval(interval)
}, [])
return <div>{count}</div>
}
Performance Optimization
Use useMemo and useCallback to optimize expensive computations and prevent unnecessary re-renders:
import { useMemo, useCallback } from 'react'
function ExpensiveComponent({ items, onItemClick }) {
// Memoize expensive computation
const total = useMemo(() => {
return items.reduce((sum, item) => sum + item.price, 0)
}, [items])
// Memoize callback
const handleClick = useCallback((id) => {
onItemClick(id)
}, [onItemClick])
return <div>Total: {total}</div>
}
Conclusion
React Hooks make functional components more powerful and easier to reason about. They eliminate much of the complexity associated with class components while providing a more composable way to share logic between components.
Start with useState and useEffect, master the rules of hooks, and gradually explore more advanced patterns like custom hooks and performance optimizations. Hooks are now the standard way to write React components, and understanding them deeply will make you a more effective React developer.