5 Principles for Effective Component Composition in React

Hello, today we will see React components concept, components that are reusable building blocks.

components: A smaller, self-contained part of a larger entity.

Composition: The act of putting together; assembly.

There are two types of components:

  • functional components
  • class components.

In this post, we’ll explore five principles for useful component composition in React that will help you build better React applications.

Table of Contents

Principle #1: Keep Components Small and Focused

// Example of a component that does too much
function UserProfile(props) {
  // ...
  return (
    <div>
      <h1>{props.name}</h1>
      <p>{props.bio}</p>
      <ul>
        {props.skills.map((skill) => (
          <li>{skill}</li>
        ))}
      </ul>
      <button onClick={props.onEdit}>Edit Profile</button>
    </div>
  );
}

// Refactored UserProfile component with single responsibility
function UserName(props) {
  return <h1>{props.name}</h1>;
}

function UserBio(props) {
  return <p>{props.bio}</p>;
}

function UserSkills(props) {
  return (
    <ul>
      {props.skills.map((skill) => (
        <li>{skill}</li>
      ))}
    </ul>
  );
}

function UserProfile(props) {
  return (
    <div>
      <UserName name={props.name} />
      <UserBio bio={props.bio} />
      <UserSkills skills={props.skills} />
      <button onClick={props.onEdit}>Edit Profile</button>
    </div>
  );
}



Principle #2: Follow the Single Responsibility Principle

Each component should be given a single responsibility, and if that responsibility changes, the component would need to change as well.

Principle #3: Use Composition Over Inheritance

Inheritance is a powerful feature of object-oriented programming, but it can lead to a tightly coupled and inflexible codebase. Composition, on the other hand, allows you to combine smaller, reusable components to create more complex components.

Using Inheritance:

import logo from './logo.svg';
import './App.css';
import React from 'react';

class ParentComponent extends React.Component {
  render() {
    return (
      <div>
        <h1>Parent Component</h1>
        {this.props.children}
      </div>
    );
  }
}

class ChildComponent extends ParentComponent {
  render() {
    return (
      <div>
        <h2>Child Component</h2>
        <p>This component inherits from ParentComponent</p>
        {this.props.children}
      </div>
    );
  }
}

function App() {
  return (
    <div>
      <ParentComponent />
      <ChildComponent>
        <p>Additional text passed as a child of ChildComponent</p>
      </ChildComponent>
    </div>
  );
}

export default App;

Using Composition:

import logo from './logo.svg';
import './App.css';
import React from 'react';

function ParentComponent(props) {
  return (
    <div>
      <h1>Parent Component</h1>
      {props.children}
    </div>
  );
}

function ChildComponent(props) {
  return (
    <div>
      <h2>Child Component</h2>
      <p>This component uses composition with ParentComponent</p>
      {props.children}
    </div>
  );
}

function App() {
  return (
    <div>
      <ParentComponent>
        <ChildComponent>
          <p>Additional text passed as a child of ChildComponent</p>
        </ChildComponent>
      </ParentComponent>
    </div>
  );
}

export default App;

In the second example, the ChildComponent uses the ParentComponent through composition, by passing it as a prop and rendering it as a child component. This allows for a more flexible and modular design, where components can be combined and composed in different ways to create complex UIs.

  • Inheritance: A way of creating a new class or component that inherits properties and methods from an existing class or component, allowing it to reuse code and extend functionality.
  • Composition: A way of combining multiple smaller components to create larger and more complex components, allowing for greater flexibility and reusability in code.

Principle #4: Avoid Tight Coupling Between Components

Tight coupling means that two components are too closely related and depend too much on each other. This can make it difficult to change one component without affecting the other.

Principle #5: Embrace Higher-Order Components

The fifth and final principle of effective component composition in React is the adoption of higher-order components (HOCs). A HOC is a function that takes a component and returns a new component with added functionality. HOC is a powerful way to share common functionality between components and reduce code duplication.

import React, { useState } from "react";

function withLoading(Component) {
  function WithLoading(props) {
    const [loading, setLoading] = useState(false);

    function handleClick() {
      setLoading(true);
      setTimeout(() => {
        setLoading(false);
      }, 2000);
    }

    return <Component {...props} loading={loading} handleClick={handleClick} />;
  }

  return WithLoading;
}

function Button(props) {
  const { label, disabled, loading, handleClick } = props;

  return (
    <button className="btn-primary" disabled={disabled || loading} onClick={handleClick}>
      {loading ? "Loading..." : label}
    </button>
  );
}

const ButtonWithLoading = withLoading(Button);

function App() {
  return (
    <div>
      <ButtonWithLoading label="Click me!" />
    </div>
  );
}

export default App;

In this example, we created an HOC called withLoading that adds loading functionality to any component that it wraps. We then wrapped the Button component with withLoading to create a new component called ButtonWithLoading.

Conclusion

By breaking components down into smaller, more focused components, using composition over inheritance, avoiding tight coupling between components, and adopting higher-order components, you can create React applications that are more flexible and easier to maintain over time.

Leave a Comment