surya@atx
// blog / react-best-practices
January 25, 2024 · Technical · 3 min read

React Best Practices in 2024

Essential patterns and practices for writing clean, maintainable React code in modern applications.

ReactJavaScriptBest Practices

React Best Practices in 2024

As React continues to evolve, so do the patterns and practices we use to build applications. Here are some best practices I've found valuable in 2024.

1. Component Composition Over Props Drilling

Instead of passing props through multiple levels, use composition:

// ❌ Avoid: Props drilling
function App() {
  const user = { name: "John" };
  return <Header user={user} />;
}

function Header({ user }) {
  return <UserProfile user={user} />;
}

// ✅ Better: Component composition
function App() {
  const user = { name: "John" };
  return (
    <Header>
      <UserProfile user={user} />
    </Header>
  );
}

function Header({ children }) {
  return <header>{children}</header>;
}

2. Custom Hooks for Logic Reuse

Extract reusable logic into custom hooks:

// useDebounce.js
import { useState, useEffect } from 'react';

export function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

// Usage
function SearchInput() {
  const [search, setSearch] = useState('');
  const debouncedSearch = useDebounce(search, 500);

  useEffect(() => {
    // Perform search with debouncedSearch
  }, [debouncedSearch]);

  return <input value={search} onChange={(e) => setSearch(e.target.value)} />;
}

3. Use TypeScript

TypeScript catches errors early and improves developer experience:

interface User {
  id: number;
  name: string;
  email: string;
}

interface UserCardProps {
  user: User;
  onEdit: (user: User) => void;
}

export function UserCard({ user, onEdit }: UserCardProps) {
  return (
    <div>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
      <button onClick={() => onEdit(user)}>Edit</button>
    </div>
  );
}

4. Keep Components Small and Focused

Single Responsibility Principle applies to components:

// ❌ Avoid: Large, multi-purpose component
function UserDashboard() {
  // Too much logic and UI in one component
}

// ✅ Better: Small, focused components
function UserDashboard() {
  return (
    <>
      <UserProfile />
      <UserStats />
      <UserActivity />
    </>
  );
}

5. Error Boundaries

Always handle errors gracefully:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}

// Usage
<ErrorBoundary>
  <App />
</ErrorBoundary>

6. Memoization When Needed

Use useMemo and useCallback for expensive operations:

function DataList({ items, filter }) {
  const filteredItems = useMemo(() => {
    return items.filter(item => item.includes(filter));
  }, [items, filter]);

  const handleClick = useCallback((id) => {
    console.log('Clicked:', id);
  }, []);

  return filteredItems.map(item => (
    <Item key={item.id} onClick={() => handleClick(item.id)} />
  ));
}

Conclusion

These practices have helped me write cleaner, more maintainable React code. Remember: write code for humans first, computers second.

What React best practices do you follow? Let me know!