React Best Practices in 2024: Modern Patterns and Performance
Discover the latest React patterns, performance optimization techniques, and architectural best practices that leading teams are using in production today.
Qivo Team
Published on November 28, 2024
const article = { topic: "React" }React continues to evolve, and so do the best practices for building production-ready applications. In 2024, the ecosystem has matured significantly with React Server Components, improved hooks patterns, and better tooling. Let's explore what modern React development looks like.
Server Components: The New Default
React Server Components (RSC) have revolutionized how we think about React applications. They allow components to run on the server, reducing bundle size and improving performance.
// app/products/page.tsx - Server Component by default
async function ProductsPage() {
const products = await db.products.findMany();
return (
<div>
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
When to Use Client Components
Use the 'use client' directive only when necessary:
- Event handlers and interactivity
- Browser APIs (localStorage, window)
- State and effects
- Custom hooks that use state
'use client';
import { useState } from 'react';
function AddToCartButton({ productId }: { productId: string }) {
const [isLoading, setIsLoading] = useState(false);
async function handleClick() {
setIsLoading(true);
await addToCart(productId);
setIsLoading(false);
}
return (
<button onClick={handleClick} disabled={isLoading}>
{isLoading ? 'Adding...' : 'Add to Cart'}
</button>
);
}
TypeScript: Non-Negotiable
TypeScript is no longer optional for serious React projects. It catches bugs at compile time and provides excellent developer experience.
Typing Component Props
interface UserCardProps {
user: {
id: string;
name: string;
email: string;
avatar?: string;
};
onEdit?: (id: string) => void;
className?: string;
}
function UserCard({ user, onEdit, className }: UserCardProps) {
return (
<div className={className}>
<h3>{user.name}</h3>
<p>{user.email}</p>
{onEdit && (
<button onClick={() => onEdit(user.id)}>Edit</button>
)}
</div>
);
}
Generic Components
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>{renderItem(item)}</li>
))}
</ul>
);
}
State Management in 2024
The React ecosystem has simplified significantly. For most applications:
Built-in Solutions
- useState/useReducer: Local component state
- Context + useReducer: App-wide state
- Server State: React Query or SWR
// Simple context for theme
const ThemeContext = createContext<{
theme: 'light' | 'dark';
toggle: () => void;
} | null>(null);
function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggle = useCallback(() => {
setTheme(t => t === 'light' ? 'dark' : 'light');
}, []);
return (
<ThemeContext.Provider value={{ theme, toggle }}>
{children}
</ThemeContext.Provider>
);
}
Data Fetching with React Query
function useProducts() {
return useQuery({
queryKey: ['products'],
queryFn: () => fetch('/api/products').then(r => r.json()),
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
function ProductList() {
const { data, isLoading, error } = useProducts();
if (isLoading) return <Skeleton />;
if (error) return <Error message={error.message} />;
return <ProductGrid products={data} />;
}
Performance Optimization
React Compiler (React Forget)
The upcoming React Compiler automatically memoizes components, reducing the need for manual useMemo and useCallback.
Manual Optimization When Needed
// Memoize expensive computations
const sortedItems = useMemo(
() => items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
// Memoize callbacks passed to children
const handleItemClick = useCallback((id: string) => {
setSelectedId(id);
}, []);
Code Splitting
import { lazy, Suspense } from 'react';
const AdminDashboard = lazy(() => import('./AdminDashboard'));
function App() {
return (
<Suspense fallback={<Loading />}>
<AdminDashboard />
</Suspense>
);
}
Testing Best Practices
Component Testing with Testing Library
import { render, screen, userEvent } from '@testing-library/react';
test('adds item to cart', async () => {
render(<AddToCartButton productId="123" />);
await userEvent.click(screen.getByRole('button'));
expect(screen.getByText('Adding...')).toBeInTheDocument();
});
Conclusion
Modern React development in 2024 is about leveraging the platform's strengths: Server Components for performance, TypeScript for reliability, and smart state management for maintainability. Focus on these fundamentals, and your applications will be well-positioned for success.
Need help modernizing your React application? Let's talk about how we can help.