DEV Community

Cover image for Top React Design Patterns Every Developer Should Know for Scalable and Efficient Apps
Ishan Bagchi
Ishan Bagchi

Posted on • Originally published at Medium

Top React Design Patterns Every Developer Should Know for Scalable and Efficient Apps

As React continues to dominate the front-end ecosystem, mastering its design patterns can significantly enhance the efficiency and scalability of your applications. React design patterns offer best practices for organizing and structuring components, managing state, handling props, and improving reusability. In this blog, we will explore some key React design patterns that can take your development process from good to great.

1. Presentational and Container Components

One of the fundamental patterns in React is the Presentational and Container Components pattern, which is all about separating concerns:

  • Presentational Components: These components are responsible for how things look. They receive data and callbacks through props but don't have logic of their own. Their sole purpose is to render UI.

  • Container Components: These components manage how things work. They contain the logic, manage state, and handle data fetching or event handling. Container components pass data to presentational components.



// Presentational Component
const UserProfile = ({ user }) => (
  <div>
    <h1>{user.name}</h1>
    <p>{user.email}</p>
  </div>
);

// Container Component
const UserProfileContainer = () => {
  const [user, setUser] = useState({ name: 'John Doe', email: 'john@example.com' });

  return <UserProfile user={user} />;
};


Enter fullscreen mode Exit fullscreen mode

This pattern encourages separation of concerns, making code easier to maintain and test.

2. Higher-Order Components (HOC)

Higher-Order Components (HOCs) are a powerful design pattern for reusing component logic. A HOC is a function that takes a component and returns a new component with enhanced behaviour or added functionality.

This pattern is commonly used for cross-cutting concerns like authentication, theming, or data fetching.



// Higher-Order Component for authentication
const withAuth = (WrappedComponent) => {
  return (props) => {
    const isAuthenticated = // logic to check auth;

    if (!isAuthenticated) {
      return <div>You need to log in!</div>;
    }

    return <WrappedComponent {...props} />;
  };
};

// Usage
const Dashboard = () => <div>Welcome to the dashboard</div>;
export default withAuth(Dashboard);


Enter fullscreen mode Exit fullscreen mode

HOCs promote DRY (Don't Repeat Yourself) principles by enabling reusable logic across multiple components.

3. Render Props

The Render Props pattern involves passing a function as a prop to a component, allowing dynamic rendering of content based on that function. This is particularly useful for sharing stateful logic between components without using HOCs.



// Render Prop Component
class MouseTracker extends React.Component {
  state = { x: 0, y: 0 };

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

  render() {
    return (
      <div onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    );
  }
}

// Usage
const App = () => (
  <MouseTracker render={({ x, y }) => <h1>Mouse position: {x}, {y}</h1>} />
);


Enter fullscreen mode Exit fullscreen mode

This pattern gives you flexibility by separating logic from UI, making components more reusable and customizable.

4. Compound Components

The Compound Component pattern is commonly used in libraries like react-select or react-table. It allows a parent component to control a group of child components. This pattern promotes flexibility in building reusable and dynamic interfaces.



// Compound Component
const Tabs = ({ children }) => {
  const [activeTab, setActiveTab] = useState(0);

  return (
    <div>
      <div>
        {children.map((child, index) => (
          <button key={index} onClick={() => setActiveTab(index)}>
            {child.props.title}
          </button>
        ))}
      </div>
      <div>{children[activeTab]}</div>
    </div>
  );
};

// Usage
<Tabs>
  <div title="Tab 1">Content of Tab 1</div>
  <div title="Tab 2">Content of Tab 2</div>
</Tabs>;


Enter fullscreen mode Exit fullscreen mode

This pattern provides a clean API for parent-child communication while keeping components flexible and customizable.

5. Controlled vs. Uncontrolled Components

React provides two ways of managing form inputs: controlled components and uncontrolled components.

  • Controlled Components: These components have their state fully controlled by React via props, which makes them more predictable.

  • Uncontrolled Components: These components rely on refs to directly manipulate the DOM, providing less control but potentially more performance.



// Controlled Component
const ControlledInput = () => {
  const [value, setValue] = useState('');

  return <input value={value} onChange={(e) => setValue(e.target.value)} />;
};

// Uncontrolled Component
const UncontrolledInput = () => {
  const inputRef = useRef();

  const handleClick = () => {
    console.log(inputRef.current.value);
  };

  return <input ref={inputRef} />;
};


Enter fullscreen mode Exit fullscreen mode

Choosing between these patterns depends on whether you need fine-grained control or lightweight performance optimizations.

6. Custom Hooks

React Hooks allows us to build custom logic in a reusable way. By extracting common logic into custom hooks, we can avoid code duplication and make our codebase more modular.



// Custom Hook
const useFetch = (url) => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

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

  return { data, error };
};

// Usage
const DataFetchingComponent = () => {
  const { data, error } = useFetch('https://api.example.com/data');

  if (error) return <p>Error: {error.message}</p>;
  if (!data) return <p>Loading...</p>;

  return <div>{data.someField}</div>;
};


Enter fullscreen mode Exit fullscreen mode

Custom hooks enable better separation of concerns and reuse of common functionality in a declarative manner.

Conclusion

Design patterns are a critical part of writing clean, maintainable, and scalable React applications. By leveraging patterns like Presentational and Container Components, HOCs, Render Props, Compound Components, and Custom Hooks, you can ensure your code is flexible, reusable, and easy to understand.

Understanding and implementing these patterns can drastically improve your development workflow, making your React projects more organized and efficient. Try incorporating them into your next project and experience the difference in both code quality and maintainability!

Top comments (0)