LearnContact
Lesson 5524 min read

Reusable Components

Learn the principles that make a component genuinely reusable, and evolve a one-off Button into a flexible, reusable component.

Introduction

The last lesson introduced the idea of designing components deliberately. This lesson goes one level deeper: what specifically makes a component reusable in practice, versus one that merely looks reusable but breaks the moment it is used somewhere slightly different?

You will walk through a realistic evolution — starting with a button hardcoded for one specific use, and gradually reshaping it into a genuinely reusable Button component — while also learning when NOT to over-engineer reusability too early.

What You Will Learn
  • The core principles behind genuinely reusable components.
  • Why single responsibility matters for reusability.
  • How sensible default props reduce friction for callers.
  • How the children prop enables flexible content.
  • The "rule of three" for deciding when to extract a reusable component.

What Makes a Component Reusable

A component is not automatically reusable just because it is a function that returns JSX. Genuine reusability comes from a handful of concrete habits, all aimed at the same goal: letting the same component serve many different call sites without being edited each time.

Single Responsibility

It does one job well, instead of trying to handle every possible variation of a feature.

Sensible Defaults

Common cases require minimal props; unusual cases can still override behavior.

Accepts children

Content inside the component can vary freely instead of being hardcoded as text.

No Hardcoded Specifics

Values that should vary between uses are props, not baked-in constants.

Single Responsibility

A reusable component should be easy to describe in one short sentence: "renders a button," "shows a labeled input," "displays a card with a title and body." If describing a component requires the word "and" more than once, it is probably doing too much to reuse cleanly.

The One-Sentence Test

If you cannot describe what a component does in a single short sentence without using "and", consider splitting it into smaller, more focused components.

Sensible Default Props

Reusable components should work well out of the box with minimal props, while still allowing customization when needed. Default parameter values in the function signature are the standard way to achieve this in modern React.

Default Props Example
function Button({ variant = 'primary', size = 'medium', children, ...rest }) {
  return (
    <button className={`btn btn-${variant} btn-${size}`} {...rest}>
      {children}
    </button>
  );
}

A caller who just needs a normal button can write <Button>Save</Button> with zero extra props, while a caller who needs something different can override variant or size explicitly.

Using children for Flexibility

A common mistake is accepting content as a text prop, like label="Save", which only works for plain text. Accepting children instead lets callers pass anything — plain text, an icon plus text, or even another component.

Limited: label prop

  • function Button({ label }) {
  • return <button>{label}</button>;
  • }
  • <Button label="Save" />
  • // Cannot add an icon next to the text

Flexible: children prop

  • function Button({ children }) {
  • return <button>{children}</button>;
  • }
  • <Button> Save</Button>
  • <Button><Spinner /> Saving...</Button>

From One-Off Button to Reusable Button

Imagine a "Save" button that started life hardcoded for one specific form, with its color, size, and label all baked in directly.

Step 1: Hardcoded, One-Off Button
function SaveButton() {
  return (
    <button style={{ background: 'blue', padding: '8px 16px', color: 'white' }}>
      Save
    </button>
  );
}

This works fine for exactly one screen. The moment another screen needs a red "Delete" button or a smaller "Cancel" button, this component cannot help — it would need to be copy-pasted and edited, creating duplicate, drifting code.

Step 2: Extracting variant and size props
function Button({ variant = 'primary', size = 'medium', children, onClick }) {
  const variantStyles = {
    primary: { background: '#3b82f6', color: 'white' },
    danger: { background: '#ef4444', color: 'white' },
    outline: { background: 'transparent', color: '#3b82f6', border: '1px solid #3b82f6' },
  };

  const sizeStyles = {
    small: { padding: '4px 10px', fontSize: '13px' },
    medium: { padding: '8px 16px', fontSize: '15px' },
    large: { padding: '12px 22px', fontSize: '17px' },
  };

  return (
    <button
      onClick={onClick}
      style={{ ...variantStyles[variant], ...sizeStyles[size], border: variantStyles[variant].border || 'none', borderRadius: '6px' }}
    >
      {children}
    </button>
  );
}

export default Button;
Step 3: One Component, Many Uses
<Button>Save</Button>
<Button variant="danger" onClick={handleDelete}>Delete</Button>
<Button variant="outline" size="small">Cancel</Button>
Rendered Output
[blue] Save     [red] Delete     [outline, small] Cancel

The same Button component now serves every screen in the app. Callers only specify what actually differs — the variant, the size, the content, and the click handler — while the rest stays consistent automatically.

The Rule of Three

It is tempting to make every component maximally reusable from day one, adding props for every imaginable future use case. In practice, this often backfires: you end up guessing at flexibility you may never need, adding complexity for no real benefit. A simpler guideline is the "rule of three."

The Rule of Three

Write the specific, hardcoded version first. If you find yourself copying a similar pattern a second time, take note. Once the same pattern appears a third time, that is a strong signal it is worth extracting into a proper reusable component with real props.

Under-Engineering

  • Copy-pasting the same JSX block into five different files.
  • Slightly different versions drift apart over time and become inconsistent.
  • Bug fixes have to be repeated in every copy.

Over-Engineering

  • Adding a dozen configuration props before there is a second real use case.
  • Guessing at flexibility that may never be needed.
  • A confusing API that is harder to use than several simple, specific components.

Just Right: Rule of Three

  • Build it simply for the first use case.
  • Notice the pattern repeating a second time.
  • Extract a clean, reusable component once it appears a third time.

Common Mistakes

Avoid These Mistakes
  • Extracting a "reusable" component after seeing a pattern only once, guessing at props it might need.
  • Using a text-only prop like label instead of children, limiting what content can be passed.
  • Hardcoding a color, size, or label inside a component that clearly needs to vary between uses.
  • Giving a component too many responsibilities, making it awkward to reuse anywhere.
  • Copy-pasting the same JSX a third time instead of finally extracting a shared component.

Best Practices

  • Start simple and specific; extract reusable components once a real pattern emerges.
  • Prefer children over text-only props whenever content might vary.
  • Give every optional prop a sensible, well-chosen default value.
  • Keep a reusable component focused on a single, clearly describable responsibility.
  • Apply the rule of three: wait for a pattern to repeat three times before extracting it.

Frequently Asked Questions

Is the rule of three a strict law?

No, it is a practical guideline. Sometimes it is obvious from the start that a component will be reused; the rule simply protects against guessing wrong too early.

Why prefer children over a text prop?

A text prop like label only accepts plain strings, while children can accept any valid JSX — icons, formatted text, or nested components — making the component far more flexible.

Does every component need default props?

Not every prop needs a default, but any prop with a common, sensible value should have one, so most callers can use the component with minimal setup.

How is this different from the Custom Components lesson?

Custom Components covered designing a thoughtful prop API in general. Reusable Components focuses specifically on the principles and timing that make a component safely reusable across a whole codebase.

What happens if I over-engineer a component too early?

You typically end up with a confusing, over-configured API built around guesses, which is often harder to use correctly than a few simple, specific components would have been.

Key Takeaways

  • Reusable components follow single responsibility, sensible defaults, and flexible content.
  • The children prop allows far more flexible content than a plain text prop.
  • A one-off Button can evolve into a reusable one through variant and size props.
  • The rule of three helps decide when a pattern is truly worth extracting.
  • Both under-engineering and over-engineering reusability carry real costs.

Summary

Genuine reusability is not about maximizing configuration options from day one — it is about single responsibility, sensible defaults, flexible content through children, and recognizing the right moment to extract a shared component. The rule of three offers a practical way to time that extraction well.

In this lesson, you learned the core principles behind reusable components, evolved a one-off Button into a properly reusable one, and weighed the tradeoffs between under- and over-engineering reusability. Next, you will apply these component-design skills to building and validating forms.

Lesson 55 Completed
  • You understand the principles that make a component genuinely reusable.
  • You evolved a hardcoded Button into a flexible, reusable one.
  • You know the rule of three for timing component extraction.
  • You are ready to move on to forms validation.
Next Lesson →

Forms Validation