Back

React Best Practices for 2024

by Jean Daly4 min read
#react#javascript#best-practices#performance

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.