React
Hooks
Patterns

Modern React Patterns

Explore the latest React patterns and hooks for better component design

Mike Johnson
2025-01-10
6 min read

Introduction

React has evolved significantly over the years, introducing powerful patterns and hooks that enable developers to write more efficient, maintainable, and reusable components. Let's explore some of the most important modern React patterns.

Custom Hooks

Custom hooks allow you to extract component logic into reusable functions, promoting code reuse and separation of concerns.

Example: useLocalStorage Hook

import { useState, useEffect } from 'react';

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      return initialValue;
    }
  });

  const setValue = (value) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue];
}

Compound Components Pattern

This pattern allows you to create components that work together to form a cohesive UI, providing flexibility while maintaining a clean API.

Example: Modal Component

const Modal = ({ children, isOpen, onClose }) => {
  if (!isOpen) return null;
  
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={e => e.stopPropagation()}>
        {children}
      </div>
    </div>
  );
};

Modal.Header = ({ children }) => (
  <div className="modal-header">{children}</div>
);

Modal.Body = ({ children }) => (
  <div className="modal-body">{children}</div>
);

Modal.Footer = ({ children }) => (
  <div className="modal-footer">{children}</div>
);

Render Props Pattern

Render props is a technique for sharing code between components using a prop whose value is a function.

Example: Data Fetcher

const DataFetcher = ({ url, render }) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch(url)
      .then(response => response.json())
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(error => {
        setError(error);
        setLoading(false);
      });
  }, [url]);

  return render({ data, loading, error });
};

Higher-Order Components (HOCs)

HOCs are functions that take a component and return a new component with additional props or behavior.

Example: withAuth HOC

const withAuth = (WrappedComponent) => {
  return function AuthenticatedComponent(props) {
    const { user, loading } = useAuth();
    
    if (loading) {
      return <div>Loading...</div>;
    }
    
    if (!user) {
      return <div>Please log in to access this page.</div>;
    }
    
    return <WrappedComponent {...props} user={user} />;
  };
};

Context and useReducer Pattern

Combining Context API with useReducer provides a powerful state management solution for complex application state.

Example: Theme Context

const ThemeContext = createContext();

const themeReducer = (state, action) => {
  switch (action.type) {
    case 'TOGGLE_THEME':
      return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
    case 'SET_THEME':
      return { ...state, theme: action.payload };
    default:
      return state;
  }
};

export const ThemeProvider = ({ children }) => {
  const [state, dispatch] = useReducer(themeReducer, { theme: 'light' });
  
  return (
    <ThemeContext.Provider value={{ state, dispatch }}>
      {children}
    </ThemeContext.Provider>
  );
};

Error Boundaries

Error boundaries catch JavaScript errors anywhere in the component tree and display a fallback UI instead of crashing the entire application.

Example: Error Boundary

class ErrorBoundary extends React.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;
  }
}

Performance Optimization Patterns

1. React.memo

Prevents unnecessary re-renders of functional components:

const ExpensiveComponent = React.memo(({ data }) => {
  return <div>{/* Expensive rendering logic */}</div>;
});

2. useMemo and useCallback

Optimize expensive calculations and prevent unnecessary function recreations:

const MyComponent = ({ items, onItemClick }) => {
  const expensiveValue = useMemo(() => {
    return items.reduce((sum, item) => sum + item.value, 0);
  }, [items]);

  const handleClick = useCallback((id) => {
    onItemClick(id);
  }, [onItemClick]);

  return <div>{/* Component JSX */}</div>;
};

Conclusion

These modern React patterns provide powerful tools for building scalable, maintainable applications. Choose the right pattern based on your specific use case, and remember that simpler solutions are often better than complex ones.