SERVICES
WORKBLOGABOUT US

May 09, 2024

Component Reusability

Web Development

Front-End Development

UX Engineering

Component Reusability Principles — Part I
Cover for Component Reusability Principles — Part I
Image source: Storyset

Introduction

Welcome to the first post of our two-part series on mastering the art of reusability in Web development using React.

In this post, we'll dig into the first three principles that are crucial for creating reusable components. The Single Responsibility Principle, Prop-Driven Design, and Keeping Props APIs Simple and Intuitive. These principles lay the groundwork for effective and scalable React applications, providing you with the essential strategies needed to enhance your development practices.

Principle 1: Single Responsibility (SRP)

Adhering to this principle is key. It dictates that a component should focus solely on one functionality, enhancing clarity and reusability.

❌ Poor Practice: All-in-One Form Component

Consider a form component that overly complicates its responsibilities:

import React, { useState } from 'react';
// A monolithic form component handling multiple responsibilities
function UserProfileForm() {
  const [userData, setUserData] = useState({ name: '', email: '' });
  const [errors, setErrors] = useState({ name: false, email: false });

  const validateEmail = (email) => { ... }
  const handleSubmit = (e) => { ... }
  const handleChange = (e) => { ... }

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Name:</label>
        <input
          name="name"
          value={userData.name}
          onChange={handleChange}
          style={{ borderColor: errors.name ? 'red' : 'initial' }}
        />
        {errors.name && <p style={{ color: 'red' }}>Name is required.</p>}
      </div>
      <div>
        <label>Email:</label>
        <input
          name="email"
          value={userData.email}
          onChange={handleChange}
          style={{ borderColor: errors.email ? 'red' : 'initial' }}
        />
        {errors.email && <p style={{ color: 'red' }}>Invalid email.</p>}
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

This implementation is a classic example of overreach—handling form state, validations, and API interactions simultaneously.

✅ Best Practice: Adhering to SRP

Consider this version where the FormInput component and the useValidation hook demonstrate the value of SRP:

function UserProfileForm() {
  const [userData, setUserData] = useState({ name: '', email: '' });

  const nameError = useValidation(userData.name, [ ... ]);
  const emailError = useValidation(userData.email, [ ... ]);

  const handleChange = (e) => { ... }
  const handleSubmit = (e) => { ... }

  return (
    <form onSubmit={handleSubmit}>
      <FormInput
        label="Name"
        name="name"
        value={userData.name}
        onChange={handleChange}
        error={nameError}
      />
      <FormInput
        label="Email"
        name="email"
        value={userData.email}
        onChange={handleChange}
        error={emailError}
      />
      <button type="submit">Submit</button>
    </form>
  );
}

The UserProfileForm component now elegantly orchestrates these simpler components, significantly boosting maintainability and readability.

For further inspiration on crafting focused and independent components, Addy Osmani's article on FIRST principles—Focused, Independent, Reusable, Small, and Testable components—is a helpful resource.

Principle 2: Prop-Driven Design

This principle enhances component flexibility by deriving behavior from props rather than internal state or inheritance.

❌ Inflexible Approach: Hardcoded Component

A component with hardcoded values is less reusable and flexible. Here's an example:

function WelcomeMessage() {
  return <h1>Welcome back, User!</h1>;
}

✅ Flexible Solution: Prop-Driven Component

Refactoring to accept dynamic content:

function WelcomeMessage({ name }) {
  return <h1>Welcome back, {name}!</h1>;
}

Now, the component can display a personalized message based on the prop it receives, enhancing its reusability across the application.

Principle 3: Keep Props APIs Simple and Intuitive

Simplicity in prop design is essential for ensuring that components are easy to integrate and maintain.

❌ Complex and Unintuitive Props API

An overly complex props structure is a common pitfall:

function UserProfile({ userData }) {
  return (
    <div>
      <h2>{userData.profile.name}</h2>
      <p>Email: {userData.profile.email}</p>
      <p>Age: {userData.details.age}</p>
      <p>Location: {userData.details.location.city}, {userData.details.location.country}</p>
    </div>
  );
}

// Usage of UserProfile requires constructing a complex, nested object
<UserProfile userData={{
    profile: { name: 'Jane Doe', email: 'jane.doe@example.com' },
    details: { age: 30, location: { city: 'New York', country: 'USA' } }
  }} 
/>

✅ Streamlined and Intuitive Props API

A better approach is to flatten the props structure and use simple, descriptive prop names. This makes the component much easier to understand and use, reducing the cognitive load on developers and minimizing the potential for mistakes.

function UserProfile({ name, email, age, city, country }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>Email: {email}</p>
      <p>Age: {age}</p>
      <p>Location: {city}, {country}</p>
    </div>
  );
}

// Usage of UserProfile is now straightforward and intuitive
<UserProfile
  name="Jane Doe"
  email="jane.doe@example.com"
  age={30}
  city="New York"
  country="USA"
/>

In the improved version, each prop is self-explanatory, and it's clear what data the component expects. By keeping the props API simple and intuitive, you enhance the developer experience, making it easier to adopt and integrate the component in various parts of the application.

Conclusion

By embracing these principles, you set a solid foundation for creating highly reusable and maintainable React components.

Stay tuned for the second part of our series, where we will explore advanced techniques like Performance and other approaches to level-up your software skills. These principles will build on what we've covered today and help you further refine your components for better performance and flexibility. Join us as we continue to unlock the full potential of React development—making sure your skills and projects stay ahead of the curve.


If you enjoyed this post, you might also like:

Torii Studio

Company

Our Story

Our Values

Our Approach

New York City

8 Spruce St,

41st Floor, SUITE F

New York, NY, 10038

Copyright © 2020-2024 Torii Studio, Inc.