16px
1.6
65ch
Advanced React Patterns for Scalable Applications

Advanced React Patterns for Scalable Applications

Exploring compound components, render props, and custom hooks for building maintainable React codebases.

React's flexibility allows for many different patterns and approaches. In this article, we dive deep into advanced patterns that help create scalable, maintainable applications.

Compound Components Pattern

Compound components allow you to create flexible and reusable component APIs. This pattern is used extensively in libraries like React Router and Reach UI.

Example Implementation:

const Tabs = ({ children, defaultTab = 0 }) => {
  const [activeTab, setActiveTab] = useState(defaultTab);
  
  return (
    <div className="tabs">
      {React.Children.map(children, (child, index) =>
        React.cloneElement(child, { activeTab, setActiveTab, index })
      )}
    </div>
  );
};

const TabList = ({ children, activeTab, setActiveTab }) => (
  <div className="tab-list">
    {React.Children.map(children, (child, index) =>
      React.cloneElement(child, { 
        isActive: activeTab === index,
        onClick: () => setActiveTab(index)
      })
    )}
  </div>
);

Render Props Pattern

Render props provide a way to share code between components using a prop whose value is a function.

Benefits:

  • Highly flexible and reusable
  • Separation of concerns
  • Testable logic isolation

Example:

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 });
};

Custom Hooks for Logic Reuse

Custom hooks extract component logic into reusable functions, following the same rules as built-in hooks.

Example: useLocalStorage Hook

const 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 saving to localStorage:', error);
    }
  };

  return [storedValue, setValue];
};

Higher-Order Components (HOCs)

While less common in modern React, HOCs still have their place for cross-cutting concerns like authentication or analytics.

Performance Optimization Patterns

React.memo and useMemo

  • Use React.memo for expensive component renders
  • useMemo for expensive calculations
  • useCallback for stable function references

Code Splitting

  • React.lazy for component-level splitting
  • Dynamic imports for route-based splitting

Conclusion

These patterns provide powerful tools for building scalable React applications. Choose the right pattern based on your specific use case and team preferences. Remember that simpler solutions are often better than complex abstractions.