Github

Creating Reusable Components in React: Best Practices

Github

In React, creating reusable components is a core skill that improves your workflow, boosts maintainability, and helps keep your code DRY (Don’t Repeat Yourself). In this guide, we'll explore best practices and practical techniques for building components that can be easily reused across your applications, enabling a scalable and clean codebase.


Introduction

As React applications grow, the importance of reusability becomes more evident. Imagine having to create the same button or form field repeatedly – not only is it time-consuming, but it also makes your codebase harder to maintain. Reusable components help solve this by letting you design once and use anywhere, making updates and styling consistent across the application.But making components truly reusable is a bit of an art. You’ll need to find the right balance between flexibility and specificity, so each component is customizable without being too complex. Here are the best practices for creating reusable React components.


1. Start with a Single Responsibility

Each component should have a single responsibility – this means it should focus on doing one thing well. For instance, a Button component should be responsible for displaying a button and handling button-specific behaviors, not form validation or layout.

tsx

type ButtonProps = {
  label: string;
  onClick: () => void;
};

const Button: React.FC<ButtonProps> = ({ label, onClick }) => (
  <button onClick={onClick} className="btn">
    {label}
  </button>
);

Keeping components focused makes them easier to reuse and test.


2. Use Props for Customization

Props allow you to customize components based on the context in which they're used. When designing a component, think about what might vary – colors, sizes, event handlers, etc. Expose these variations through props rather than hardcoding them.

tsx

type CardProps = {
  title: string;
  content: string;
  bgColor?: string;
};

const Card: React.FC<CardProps> = ({ title, content, bgColor = 'white' }) => (
  <div style={{ backgroundColor: bgColor }} className="card">
    <h2>{title}</h2>
    <p>{content}</p>
  </div>
);

In this example, Card allows background color customization, making it more flexible.


3. Avoid Hardcoding Styles – Use Props for Styling

Hardcoding styles can make your components less adaptable. Instead, use props for styling whenever possible, especially for components with multiple variations. Tools like styled-components or CSS modules can also help keep styles isolated and modular.

tsx

type ButtonProps = {
  label: string;
  variant: 'primary' | 'secondary';
  onClick: () => void;
};

const Button: React.FC<ButtonProps> = ({ label, variant, onClick }) => (
  <button className={`btn ${variant}`} onClick={onClick}>
    {label}
  </button>
);

By passing in a variant prop, we allow easy style customization without duplicating component code.


4. Embrace Composition Over Inheritance

React favors composition over inheritance. Rather than building a large, complex component with lots of variations, create smaller components and compose them together. This keeps components simpler and encourages reusability.

tsx

const Modal: React.FC = ({ children }) => (
  <div className="modal">
    <div className="modal-content">{children}</div>
  </div>
);

// Usage
<Modal>
  <h2>Modal Title</h2>
  <p>Modal content goes here...</p>
</Modal>

Here, Modal wraps any children passed to it, allowing you to control its content without modifying the component itself.


5. Use Custom Hooks for Shared Logic

When components share similar behavior, such as data fetching or state management, a custom hook can encapsulate that logic and keep the components lean.

tsx

import { useState, useEffect } from 'react';

function useFetchData(url: string) {
  const [data, setData] = useState(null);

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

  return data;
}

// Component usage
const DataDisplay: React.FC<{ url: string }> = ({ url }) => {
  const data = useFetchData(url);
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
};

This custom hook handles data fetching logic, keeping the DataDisplay component focused on rendering.


6. Avoid Overly Generic Components

It’s tempting to make a component that does everything, but this can lead to bloat and complexity. Avoid creating components that require many configuration props or have too many conditional renders. Instead, build specific components that can be composed or customized.

tsx

// Avoid:
const Card = ({ title, imageUrl, content, footer, isClickable }) => {
  // Too many variations – becomes hard to maintain.
};

Instead, break it down into smaller components, like Image, Content, and Footer, and then compose them as needed.


7. Use Default Props and TypeScript for Strong Typing

Setting default props ensures components have sensible defaults, and using TypeScript enforces consistency across the application. Strong typing helps catch errors early and makes components more predictable.

tsx

type ButtonProps = {
  label: string;
  onClick?: () => void;
};

const Button: React.FC<ButtonProps> = ({ label, onClick = () => {} }) => (
  <button onClick={onClick} className="btn">
    {label}
  </button>
);

In this example, onClick is optional with a default, ensuring there’s always a valid function when it’s not passed.


8. Document and Use Prop Descriptions

Documenting component props is a best practice that helps others understand your component’s requirements and options. Tools like Storybook are useful for visually documenting components, but even inline comments can help.

tsx

type TooltipProps = {
  text: string; // Text to display inside the tooltip
  position?: 'top' | 'bottom' | 'left' | 'right'; // Position of the tooltip
};

Well-documented components make development faster and smoother for your team.


Conclusion

Creating reusable components in React isn’t just about reusability—it’s about making code more maintainable, predictable, and easier to work with. By following these best practices, you can create components that are not only reusable but also flexible, adaptable, and easy to understand.Start small, and as your project grows, these techniques will help you maintain a clean and organized codebase, whether you’re working alone or with a team. Happy coding!