No history yet

Advanced Component Patterns

Higher-Order Components

A Higher-Order Component, or HOC, is a function that takes a component as an argument and returns a new component. Think of it as a wrapper. It wraps your component to give it extra abilities or props without changing the original component's code.

The core idea is simple: an HOC is a pure function with zero side-effects that accepts a component and returns a new, enhanced component.

This pattern is useful for reusing component logic. For example, you might have several components that need access to the current window size. Instead of adding that logic to every single component, you can create an HOC to handle it.

Let's build a simple HOC called withLogger. It will log a component's props to the console whenever it renders. This is great for debugging.

import React, { useEffect } from 'react';

function withLogger(WrappedComponent) {
  // The HOC returns a new component
  return function WithLoggerComponent(props) {
    useEffect(() => {
      console.log('Props:', props);
    });

    // Render the original component with its props
    return <WrappedComponent {...props} />;
  };
}

// A simple component to wrap
function UserProfile({ name, age }) {
  return (
    <div>
      <p>Name: {name}</p>
      <p>Age: {age}</p>
    </div>
  );
}

// Create the enhanced component by calling the HOC
const UserProfileWithLogger = withLogger(UserProfile);

// You can now use <UserProfileWithLogger /> in your app
// export default UserProfileWithLogger;

In this example, withLogger is the HOC. It takes UserProfile and returns WithLoggerComponent, a new component that logs its own props and then renders UserProfile.

HOCs are powerful, but they have a couple of drawbacks. Props passed by the HOC can conflict with the component's own props if they have the same name. Also, using many HOCs on one component can lead to deeply nested components in the React DevTools, sometimes called "wrapper hell."

Render Props

The term "render prop" refers to a technique for sharing code between React components using a prop whose value is a function. A component with a render prop takes a function that returns a React element and calls it instead of implementing its own render logic.

This pattern inverts control. The component manages the state or logic, but it lets another component decide what to render. It makes the data flow very explicit.

Lesson image

Let's create a MouseTracker component. Its only job is to track the mouse's (x, y) coordinates on the screen. It won't render anything itself; it will use a render prop to let its parent decide how to display the coordinates.

import React, { useState } from 'react';

function MouseTracker({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const handleMouseMove = (event) => {
    setPosition({
      x: event.clientX,
      y: event.clientY
    });
  };

  return (
    <div style={{ height: '100vh' }} onMouseMove={handleMouseMove}>
      {/* Call the render prop with the current state */}
      {render(position)}
    </div>
  );
}

// How to use the MouseTracker component
function App() {
  return (
    <div>
      <h1>Move the mouse around!</h1>
      <MouseTracker 
        render={mouse => (
          <p>The mouse position is ({mouse.x}, {mouse.y})</p>
        )}
      />
    </div>
  );
}

Here, MouseTracker handles all the logic for tracking the mouse. The App component uses MouseTracker and provides a function to the render prop. This function receives the mouse state and returns a <p> tag to display it. The name of the prop doesn't have to be render. Any prop that is a function and tells the component what to render is a render prop.

Compound Components

The compound component pattern is a way to create components that work together to manage a shared state and behavior. Think of how HTML's <select> and <option> tags work. They are useless on their own but create a complete UI element when used together.

This pattern allows you to create expressive and declarative components. The parent component manages the state, and the child components implicitly communicate with the parent to form a cohesive unit. The modern way to implement this is with the React Context API.

Let's build a simple <Accordion> component. It will have a parent Accordion that manages which panel is open, and child components like Accordion.Item, Accordion.Header, and Accordion.Panel.

import React, { useState, useContext, createContext } from 'react';

// 1. Create a Context
const AccordionContext = createContext();

// 2. Create the Parent Component
function Accordion({ children }) {
  const [activeIndex, setActiveIndex] = useState(null);

  const toggleIndex = (index) => {
    setActiveIndex(activeIndex === index ? null : index);
  };

  return (
    <AccordionContext.Provider value={{ activeIndex, toggleIndex }}>
      {children}
    </AccordionContext.Provider>
  );
}

// 3. Create Child Components that use the Context
function AccordionItem({ index, children }) {
  return <div>{children}</div>;
}

function AccordionHeader({ index, children }) {
  const { toggleIndex } = useContext(AccordionContext);
  return <div onClick={() => toggleIndex(index)}>{children}</div>;
}

function AccordionPanel({ index, children }) {
  const { activeIndex } = useContext(AccordionContext);
  return activeIndex === index ? <div>{children}</div> : null;
}

// How to use the Accordion
function App() {
  return (
    <Accordion>
      <AccordionItem index={0}>
        <AccordionHeader index={0}>Panel 1</AccordionHeader>
        <AccordionPanel index={0}>Content for panel 1</AccordionPanel>
      </AccordionItem>
      <AccordionItem index={1}>
        <AccordionHeader index={1}>Panel 2</AccordionHeader>
        <AccordionPanel index={1}>Content for panel 2</AccordionPanel>
      </AccordionItem>
    </Accordion>
  );
}

The Accordion component creates a context and provides the activeIndex and the toggleIndex function to all its children. The AccordionHeader can then call toggleIndex when clicked, and the AccordionPanel knows whether to show itself based on the activeIndex. This creates a clean and reusable API for developers using your component.

Quiz Questions 1/6

What is the primary function of a Higher-Order Component (HOC) in React?

Quiz Questions 2/6

A common issue when wrapping a component with multiple HOCs is the creation of a deeply nested component tree in the React DevTools. What is this issue commonly called?

These advanced patterns provide powerful ways to create flexible, reusable, and maintainable components in your React applications.