Select box component with React and TypeScript

January 21, 2023React

4 min read

In this article, we'll be creating a custom select component with React and TypeScript. We'll call this component SelectBox. It will render a customizable HTML select element with React.

The advantages of extracting the HTML select element into our own custom SelectBox component are the following:

  • We can re-use the same component across many applications.
  • Styles and tests can live alongside the component.
  • All our select boxes are configured via one standard interface.

Let's create the SelectBox component. We'll create a components folder and then create a SelectBox folder within it. In this SelectBox folder, we'll create a SelectBox.tsx file for our React component. Let's add the following code in this file.

SelectBox.tsx
import * as React from 'react';
import { ChangeEvent } from 'react';
import styles from './SelectBox.module.css';

type SelectOption = {
  label: string;
  value: string;
};

type Props = {
  value?: string;
  disabled?: boolean;
  className?: string;
  options: SelectOption[];
  onChange: (e: ChangeEvent<HTMLSelectElement>) => void;
};

const SelectBox = ({
  value,
  disabled,
  className,
  options,
  onChange,
}: Props) => {
  const selectBox = (
    <select className={className} disabled={disabled} onChange={onChange} value={value}>
      {options.map(({ value, label }) => (
        <option key={value} value={value}>
          {label}
        </option>
      ))}
    </select>
  );

  return <div className={styles.SelectBox}>{selectBox}</div>;
};

export { SelectBox };
export type { SelectOption };

Let's create a SelectBox.module.css file to be the CSS Module for styling the SelectBox component. Let's add some basic CSS styling to the select box via the following classes.

SelectBox.module.css
.SelectField {
  width: 100%;
}

.Label {
  margin: 0 0 0.5rem;
}

select {
  width: 100%;
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 0.5rem;
}

select:focus {
  border-color: #000;
  outline: 0;
}

We can now start using our SelectBox component within our React application. For testing purposes, let's implement the SelectBox component within the main App component of our app.

App.tsx
import * as React from 'react';
import { useState, ChangeEvent } from 'react';
import { SelectBox } from './components';
import type { SelectOption } from './components';

const countries = ['Canada', 'Mexico', 'USA'];

export default function App() {
  const options: SelectOption[] = [
    { label: 'Select...', value: '' },
    ...countries.map((country) => ({ label: country, value: country })),
  ];
  const [value, setValue] = useState('');

  const onChange = (event: ChangeEvent<HTMLSelectElement>) => {
    setValue(event.target.value);
  };

  return (
    <div>
      <h1>Select Box</h1>
      <SelectBox
        options={options}
        value={value}
        onChange={onChange}
      />
      {value && <p>Selected value: {value}</p>}
    </div>
  );
}

Wrapping the select box in a label

We can modify our SelectBox component to include a label.

There are two ways to combine a HTML label with a HTML form element. The first is by wrapping the element in a label, and the other is by adding a for attribute to the label and an id attribute to the element.

Let's go with the first approach and wrap our HTML select box in a HTML label. This allows us to avoid explicitly adding the for attribute on the label. We can also avoid explicitly adding an id attribute on the select box.

Let's create a custom React Label component for the label.

Label.tsx
import * as React from 'react';
import { ReactNode } from 'react';
import styles from './Label.module.css';

type Props = {
  children?: ReactNode;
};

const Label = ({ children }: Props) => {
  return <label className={styles.Label}>{children}</label>;
};

export { Label };

Now, we can make use of the Label component in our SelectBox component. Here's what the updated SelectBox component looks like.

SelectBox.tsx
type SelectOption = {
  label: string;
  value: string;
};

type Props = {
  value?: string;
  label?: string; // new `label` prop
  disabled?: boolean;
  className?: string;
  options: SelectOption[];
  onChange: (e: ChangeEvent<HTMLSelectElement>) => void;
};

const SelectBox = ({
  value,
  label,
  disabled,
  className,
  options,
  onChange,
}: Props) => {
  const selectBox = (
    <select
      className={className}
      disabled={disabled}
      onChange={onChange}
      value={value}
    >
      {options.map(({ value, label }) => (
        <option key={value} value={value}>
          {label}
        </option>
      ))}
    </select>
  );

  const result = label ? (
    <Label>
      <div className={styles.Label}>{label}</div>
      {selectBox}
    </Label>
  ) : (
    selectBox
  );

  return <div className={styles.SelectBox}>{result}</div>;
};

Take a look at the demo below to see how we can now pass a string to the label prop of the SelectBox component to enable the Label.

Demo - SelectBox and Label