Select box component with React and TypeScript
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.
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.
.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.
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.
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.
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
.