React Best Practices for 2024
React continues to evolve, and with it, the best practices for building robust applications. Here are the essential patterns and practices every React developer should follow in 2024.
1. Use Functional Components and Hooks
Functional components with hooks are now the standard:
// ✅ Good - Functional component with hooks
function UserProfile({ userId }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
fetchUser(userId).then(setUser).finally(() => setLoading(false))
}, [userId])
if (loading) return <div>Loading...</div>
return <div>{user?.name}</div>
}
// ❌ Avoid - Class components (unless necessary)
class UserProfile extends Component {
// ... class component code
}
2. Proper State Management
Keep State Close to Where It's Used
// ✅ Good - State close to usage
function TodoList() {
const [todos, setTodos] = useState([])
const [filter, setFilter] = useState('all')
// Component logic here
}
// ❌ Avoid - Unnecessary prop drilling
function App() {
const [todos, setTodos] = useState([])
return <TodoList todos={todos} setTodos={setTodos} />
}
Use Context for Global State
// ✅ Good - Context for global state
const ThemeContext = createContext()
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
)
}
3. Performance Optimization
Use React.memo for Expensive Components
// ✅ Good - Memoized component
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
const processedData = useMemo(() => {
return data.map(item => expensiveCalculation(item))
}, [data])
return <div>{processedData}</div>
})
Optimize useEffect Dependencies
// ✅ Good - Proper dependencies
function UserProfile({ userId }) {
const [user, setUser] = useState(null)
useEffect(() => {
if (userId) {
fetchUser(userId).then(setUser)
}
}, [userId]) // Only re-run when userId changes
return <div>{user?.name}</div>
}
4. Component Composition
Use Composition Over Inheritance
// ✅ Good - Composition pattern
function Card({ children, className = '' }) {
return (
<div className={`card ${className}`}>
{children}
</div>
)
}
function UserCard({ user }) {
return (
<Card className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
</Card>
)
}
Render Props Pattern
// ✅ Good - Render props for reusable logic
function DataFetcher({ url, children }) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false))
}, [url])
return children({ data, loading, error })
}
// Usage
<DataFetcher url="/api/users">
{({ data, loading, error }) => {
if (loading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return <UserList users={data} />
}}
</DataFetcher>
5. Error Handling
Use Error Boundaries
class ErrorBoundary extends Component {
constructor(props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error) {
return { hasError: true }
}
componentDidCatch(error, errorInfo) {
console.error('Error caught by boundary:', error, errorInfo)
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>
}
return this.props.children
}
}
6. Testing Best Practices
Write Tests That Resemble User Behavior
// ✅ Good - Testing user interactions
import { render, screen, fireEvent } from '@testing-library/react'
test('should increment counter when button is clicked', () => {
render(<Counter />)
const button = screen.getByRole('button', { name: /increment/i })
const counter = screen.getByText('0')
fireEvent.click(button)
expect(screen.getByText('1')).toBeInTheDocument()
})
7. TypeScript Integration
Use Proper TypeScript Types
// ✅ Good - Proper TypeScript usage
interface User {
id: string
name: string
email: string
}
interface UserCardProps {
user: User
onEdit?: (user: User) => void
}
function UserCard({ user, onEdit }: UserCardProps) {
return (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
{onEdit && (
<button onClick={() => onEdit(user)}>Edit</button>
)}
</div>
)
}
Conclusion
Following these React best practices will help you:
- Write more maintainable code
- Improve application performance
- Reduce bugs and improve reliability
- Create better user experiences
Remember, best practices evolve with the ecosystem. Stay updated with the React community and official documentation to keep your skills sharp and your applications modern.