Mastering React Design Patterns: From Containers to Custom Hooks

This article reviews essential React design patterns—including container/presentational components, higher‑order components, render props, compound components, and custom hooks—explaining the underlying SOLID principles, providing clear code examples, and offering guidance on when to apply each pattern.

Alibaba Cloud Developer
Alibaba Cloud Developer
Alibaba Cloud Developer
Mastering React Design Patterns: From Containers to Custom Hooks

Design patterns are reusable solutions to common problems and are considered best practices for writing maintainable, robust code. This article reviews several React community design patterns and the design principles they follow.

Key Design Principles

Single Responsibility Principle : each class, function, or module should have only one responsibility.

Open‑Closed Principle : entities should be open for extension but closed for modification.

Dependency Inversion Principle : depend on abstractions rather than concrete implementations.

Don’t Repeat Yourself (DRY) : avoid duplicated code to simplify maintenance.

Composition over Inheritance : prefer composing objects to inheriting from them.

React Design Patterns

Container & Presentational Components

Separate UI rendering (presentational) from data fetching and event handling (container). This respects the Single Responsibility Principle and DRY.

Example

import React from "react";
// Presentational component
export default function ImageList({ images, onClick }) {
  return images.map((img, i) => <img src={img} key={i} onClick={onClick} />);
}

// Container component
export default class ImageListContainer extends React.Component {
  constructor() {
    super();
    this.state = { images: [] };
  }
  componentDidMount() {
    fetch("https://images.com")
      .then(res => res.json())
      .then(({ images }) => this.setState({ images }));
  }
  handleClick() {
    // ...
  }
  render() {
    return <ImageList images={this.state.images} onClick={this.handleClick} />;
  }
}

Higher‑Order Component (HOC)

A function that takes a component and returns a new component, enabling logic reuse. Used by Redux’s connect and Relay’s createFragmentContainer.

Principles

DRY: encapsulate reusable logic in the HOC.

Composition over Inheritance: HOCs can be nested.

Example

import React from "react";
export default function withLoader(Component, url) {
  return class HOC extends React.Component {
    constructor(props) {
      super(props);
      this.state = { loading: true, data: {} };
    }
    componentDidMount() {
      fetch(url)
        .then(res => res.json())
        .then(({ data }) => this.setState({ data }))
        .finally(() => this.setState({ loading: false }));
    }
    render() {
      if (this.state.loading) {
        return <div>Loading...</div>;
      }
      return <Component {...this.props} data={this.state.data} />;
    }
  };
}

Render Prop

Components expose a function prop that allows callers to customize rendering logic. Libraries such as React Router, Downshift, and Formik use this pattern.

Principles

DRY: move reusable rendering logic into the component.

Dependency Inversion: inject rendering behavior via the prop.

Open‑Closed: the component provides extension points without modification.

Example

import React from "react";
class Loader extends React.Component {
  constructor(props) {
    super(props);
    this.state = { loading: true, data: {} };
  }
  componentDidMount() {
    fetch(url)
      .then(res => res.json())
      .then(({ data }) => this.setState({ data }))
      .finally(() => this.setState({ loading: false }));
  }
  render() {
    if (this.state.loading) {
      return <div>Loading...</div>;
    }
    return this.props.renderData(this.state.data);
  }
}

Compound Components

Multiple components work together through shared state or context to accomplish a task, e.g., Select and Select.Option. Libraries like Semantic UI use this pattern.

Principles

Single Responsibility: each sub‑component handles its own concern.

Open‑Closed: new sub‑components can extend functionality without altering existing ones.

Example

import React from "react";
const SelectContext = React.createContext({});
export function Select({ value, onChange, children }) {
  const [open, setOpen] = React.useState(false);
  const [val, setValue] = React.useState(value);
  return (
    <div className="select">
      <div className="select-value" onClick={() => setOpen(true)}>{val}</div>
      <SelectContext.Provider value={{ value: val, setOpen, setValue: newValue => { setValue(newValue); if (value !== newValue) onChange(newValue); } }}>
        {open && children}
      </SelectContext.Provider>
    </div>
  );
}
function Option({ children, value }) {
  const { setOpen, setValue, value: selectedValue } = React.useContext(SelectContext);
  return (
    <div className={`select-option ${value === selectedValue ? "selected" : ""}`} onClick={() => { setValue(value); setOpen(false); }}>
      {children}
    </div>
  );
}
function OptionGroup({ children, label }) {
  return (
    <div className="select-option-group">
      <div className="select-option-group-label">{label}</div>
      {children}
    </div>
  );
}
Select.Option = Option;
Select.OptionGroup = OptionGroup;

Custom Hooks

Custom hooks encapsulate reusable stateful logic, offering a finer‑grained alternative to component‑based patterns.

Principles

DRY: place reusable logic in a hook.

Single Responsibility: each hook focuses on a single concern.

Example

import { useState, useEffect } from "react";
function useLoader(url) {
  const [data, setData] = useState({});
  const [loading, setLoading] = useState(false);
  useEffect(() => {
    setLoading(true);
    fetch(url)
      .then(res => res.json())
      .then(({ data }) => setData({ data }))
      .finally(() => setLoading(false));
  }, [url]);
  return { data, loading };
}

Conclusion

The design patterns discussed follow SOLID principles and help developers write robust, maintainable code, but they should be applied judiciously; the YAGNI principle reminds us to avoid unnecessary complexity.

References

https://reactjs.org/docs/composition-vs-inheritance.html

https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

https://github.com/reduxjs/react-redux/blob/master/docs/api/connect.md#connect

https://relay.dev/docs/v10.1.3/fragment-container/#createfragmentcontainer

https://reactjs.org/docs/composition-vs-inheritance.html

https://reacttraining.com/react-router/web/api/Route/render-func

https://github.com/paypal/downshift

https://github.com/jaredpalmer/formik

https://react.semantic-ui.com/

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsfrontendReactComponent ArchitecturehooksSOLID principlesHigher-Order Components
Alibaba Cloud Developer
Written by

Alibaba Cloud Developer

Alibaba's official tech channel, featuring all of its technology innovations.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.