REACT Lesson 41 – Advanced React Patterns | Dataplexa
LESSON 41

Advanced React Patterns

Master compound components, render props, higher-order components, and custom hooks to build flexible, reusable UI systems.

React patterns solve real problems. When you build the same component five times with slight variations, patterns help you create one flexible solution. These aren't academic concepts — they're tools Netflix uses for their video player controls and Airbnb uses for their booking forms.

Compound Components

A compound component works like HTML's select and option tags. The parent manages state while children handle their own rendering. React's Context API makes this possible. Think of it like a restaurant menu. The menu container holds all the logic, but each menu item knows how to display itself:
// Menu compound component for DataFlow filters
const Menu = ({ children, onSelect }) => {
  const [isOpen, setIsOpen] = useState(false);
  const [selected, setSelected] = useState('');

  const value = { isOpen, setIsOpen, selected, setSelected, onSelect };

  return (
    <MenuContext.Provider value={value}>
      <div className="menu">{children}</div>
    </MenuContext.Provider>
  );
};
DataFlow Dashboard
What just happened?
The Menu component shares state through Context while each subcomponent handles its own rendering logic. The dot notation (Menu.Toggle) creates a clean API that feels like native HTML elements. Try this: Add more Menu.Item components to see how flexible this pattern becomes.

Render Props Pattern

Render props let you share code between components using a prop whose value is a function. Instead of guessing what UI someone wants, you let them tell you. The DataFlow team needs flexible data loading. Sometimes they want a spinner, sometimes a progress bar, sometimes nothing. Render props solve this:
// Flexible data fetcher with render props
const DataFetcher = ({ url, render }) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Simulate API call
    setTimeout(() => {
      setData({ revenue: 45200, users: 1340 });
      setLoading(false);
    }, 1500);
  }, [url]);

  return render({ data, loading, error });
};
DataFlow Dashboard
What just happened?
The DataFetcher component handles all the loading logic but lets you decide how to render the UI. The render prop receives data and lets you create any UI you want. Try this: Change the render function to show a simple list instead of cards.

Higher-Order Components (HOCs)

A higher-order component is a function that takes a component and returns a new component with extra functionality. Think of it like a decorator pattern — you wrap components to add features. Authentication is perfect for HOCs. The DataFlow admin panel needs to check if users are logged in before showing certain components:
// HOC for authentication in DataFlow
const withAuth = (WrappedComponent) => {
  return function AuthComponent(props) {
    const [isLoggedIn, setIsLoggedIn] = useState(false);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
      // Simulate auth check
      setTimeout(() => {
        setIsLoggedIn(Math.random() > 0.3); // 70% chance logged in
        setLoading(false);
      }, 1000);
    }, []);

    if (loading) return <div>Checking authentication...</div>;
    if (!isLoggedIn) return <div>Please log in to access DataFlow</div>;
    
    return <WrappedComponent {...props} />;
  };
};
DataFlow Dashboard
What just happened?
The withAuth HOC wraps any component and adds authentication logic. The wrapped component only renders when the user is logged in — otherwise it shows an access denied message. Try this: Refresh the page multiple times to see different authentication states.

Custom Hook Patterns

Custom hooks extract stateful logic into reusable functions. They're like regular functions but can use other hooks inside them. Every custom hook starts with "use" — that's the rule. The DataFlow team needs to fetch data from multiple endpoints. Instead of copying useEffect code everywhere, create a custom hook:
// Custom hook for DataFlow API calls
const useDataFlowAPI = (endpoint, defaultValue = null) => {
  const [data, setData] = useState(defaultValue);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  const refetch = useCallback(() => {
    setLoading(true);
    setError(null);
    
    // Simulate API call with realistic delay
    setTimeout(() => {
      const mockData = {
        '/stats': { revenue: 89400, users: 2340, orders: 156 },
        '/users': [{ id: 1, name: 'Sarah Chen', role: 'Admin' }],
        '/orders': [{ id: 101, amount: 299, status: 'completed' }]
      };
      
      setData(mockData[endpoint] || defaultValue);
      setLoading(false);
    }, Math.random() * 1000 + 500);
  }, [endpoint, defaultValue]);

  useEffect(() => {
    refetch();
  }, [refetch]);

  return { data, loading, error, refetch };
};
DataFlow Dashboard
What just happened?
The useDataFlowAPI custom hook encapsulates all the data fetching logic. Both components use the same hook but with different endpoints. Each component gets its own state and can refetch independently. Try this: Click the refresh buttons to see independent loading states.

Provider Pattern

The Provider pattern uses React Context to share data across many components without prop drilling. Think of it as a global state that any component can access. DataFlow needs a theme system. Users should switch between light and dark modes anywhere in the app:
// Theme provider for DataFlow
const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');
  const [primaryColor, setPrimaryColor] = useState('#047857');

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  const value = {
    theme,
    primaryColor,
    toggleTheme,
    setPrimaryColor,
    colors: theme === 'light' 
      ? { bg: '#ffffff', text: '#1f2937', border: '#e5e7eb' }
      : { bg: '#1f2937', text: '#f9fafb', border: '#374151' }
  };

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
};
DataFlow Dashboard
What just happened?
The ThemeProvider wraps the entire app and provides theme data to any component that needs it. Components use the useTheme hook to access colors and toggle functions without prop drilling. Try this: Click the theme toggle to see all components update their styling instantly.
Pattern Selection Guide
Compound Components: When you need flexible composition (like HTML select/option)
Render Props: When you want to share logic but let consumers control rendering
HOCs: When you need to add the same functionality to multiple components
Custom Hooks: When you want to share stateful logic between components
Provider: When many components need the same data (auth, theme, settings)
Real apps combine these patterns. Netflix uses render props for their video player, compound components for navigation menus, and providers for user authentication. Linear uses custom hooks for their API calls and HOCs for permission checking. The key is knowing when each pattern solves your specific problem. Start simple, then add patterns when you find yourself copying code or drilling props through many levels.

Quiz

1. How do compound components work in the DataFlow menu system?


2. What is a Higher-Order Component (HOC) in the DataFlow authentication system?


3. What's the main difference between custom hooks and render props for the DataFlow API system?


React with TypeScript

Add type safety to your React components and catch errors before they reach production.