Skip to content
On this page

Reusable React Component

Have a closer look at the Search component: Every implementation detail is tied to the search feature. However, internally the component is only a label and an input, so why should it be tied so strictly to one domain? Being tied to one feature makes a component less reusable for the rest of the application. In this case, the Search component cannot be used for non-search-related tasks.

In addition, the Search component risks introducing bugs, because if two of these Search components are rendered on the same page, their htmlFor/id combination is duplicated and therefore breaking the focus when one of the labels is clicked by the user. Let's fix these underlying issues by making the Search component reusable.

Since the Search component doesn't have any actual "search" functionality, it takes little effort to generalize the search domain-specific properties to make the component reusable for the rest of the application. Let's pass a dynamic id and label prop to the Search component, rename the actual value and callback handler to something more generic, and rename the component too:

src/App.jsx

jsx
const App = () => {
  ...

  return (
    <div>
      <h1>My Hacker Stories</h1>

      <InputWithLabel
        id="search"
        label="Search"
        value={searchTerm}
        onInputChange={handleSearch}
      />

      ...
    </div>
  );
};

const InputWithLabel = ({ id, label, value, onInputChange }) => (
  <>
    <label htmlFor={id}>{label}</label>
    &nbsp;
    <input
      id={id}
      type="text"
      value={value}
      onChange={onInputChange}
    />
  </>
);

It's fully reusable, but only when using an input with a text. If we would want to support numbers (number) or phone numbers (tel) too, the type attribute of the input field needs to be accessible from the outside too:

src/App.jsx

jsx
const InputWithLabel = ({
  id,
  label,
  value,
  type = 'text',
  onInputChange,
}) => (
  <>
    <label htmlFor={id}>{label}</label>
    &nbsp;
    <input
      id={id}
      type={type}
      value={value}
      onChange={onInputChange}
    />
  </>
);

Because we don't pass a type prop from the App component to the InputWithLabel component, the default parameter from the function signature takes over for the type. Thus, every time the InputWithLabel component is used without a type prop, the default type will be "text".

With just a few changes we turned a specialized Search component into a more reusable InputWithLabel component. We generalized the naming of the internal implementation details and gave the new component a larger API surface to provide all the necessary information from the outside. We aren't using the component elsewhere yet, but we increased its ability to handle the task if we do.

It's always a trade-off between generalization and specialization of components. In this case, we turned a highly specialized component into a generalized component. While a generalized component has a better chance of getting reused in the application, a specialized component would implement business logic for one specific use case and therefore isn't reusable at all.

Exercises: