Back to blog
ReactFrontendPerformanceTypeScript

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.

Q

Qivo Team

Published on November 28, 2024

4 min read
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.