0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Material-UIでSearchableSelectを作る

Posted at

概要

  • タイトル通りですが、Material-UIでSearchableなSelectコンポーネントを実装したので記事にしてみました
  • ついでにClearableも実装しています
  • 下記2点ができてないです(T_T)
    • 選択時にTextFieldにFocusがかかるようにする
    • 特定条件でのwarningの除去
  • 各バージョン
{
    "typescript": "^4.9.5"
    "react": "^18.2.0"
    "@mui/material": "^5.11.10"
}

詳細

  • 呼び出し元
  <SearchableSelect
    label="都道府県"
    setValue={setPrefectures}
    options={prefecturesList}
  />
  • SearchableSelectコンポーネント
import React, { useMemo, useState } from "react";
import {
  Box,
  FormControl,
  Select as MuiSelect,
  MenuItem,
  InputLabel,
  ListSubheader,
  TextField,
  InputAdornment,
  type SelectChangeEvent,
  IconButton,
} from "@mui/material";
import SearchIcon from "@mui/icons-material/Search";
import ClearIcon from "@mui/icons-material/Clear";

interface ISelectObject {
  value: string;
  label: string;
}

interface IProps {
  label: string;
  options: ISelectObject[];
  setValue: React.Dispatch<React.SetStateAction<string>>;
  isClearable?: boolean;
}

const containsText = (text: string, searchText: string): boolean =>
  text.toLowerCase().includes(searchText.toLowerCase());

const SearchableSelect: React.FC<IProps> = (props) => {
  // HACK: TextFieldにFocusがかかるようにする(autoFocusつけたり色々試したができなかった)
  // HACK: 何か選択済みで検索をかけるとwarningが表示される
  //       material-uiが選択済みの状態でoptionがfilterされるのを考慮していないため

  const { label, options, setValue, isClearable = true } = props;
  const [displayValue, setDisplayValue] = useState("");
  const [searchText, setSearchText] = useState("");

  const displayedOptions = useMemo(
    () => options.filter((option) => containsText(option.label, searchText)),
    [searchText, options]
  );

  const handleChange = (event: SelectChangeEvent<string>): void => {
    const selectedObject = JSON.parse(event.target.value) as ISelectObject;
    setValue(selectedObject.value);
    setDisplayValue(selectedObject.label);
  };

  const handleClearClick = (): void => {
    setValue("");
    setDisplayValue("");
  };

  return (
    <Box sx={{ m: 2 }}>
      <FormControl fullWidth>
        <InputLabel id="searchable-select-label">{label}</InputLabel>
        <MuiSelect
          id="searchable-select-id"
          labelId="searchable-select-label"
          label={label}
          onChange={handleChange}
          onClose={() => {
            setSearchText("");
          }}
          defaultValue=""
          renderValue={() => displayValue}
          endAdornment={
            isClearable ? (
              <IconButton
                sx={{
                  display: displayValue === "" ? "none" : "inline-flex",
                  marginRight: "10px",
                }}
                onClick={handleClearClick}
              >
                <ClearIcon />
              </IconButton>
            ) : (
              <></>
            )
          }
        >
          <ListSubheader>
            <TextField
              fullWidth
              size="small"
              placeholder="検索"
              InputProps={{
                startAdornment: (
                  <InputAdornment position="start">
                    <SearchIcon />
                  </InputAdornment>
                ),
              }}
              onChange={(e) => {
                setSearchText(e.target.value);
              }}
              onKeyDown={(e) => {
                if (e.key !== "Escape") {
                  // Prevents autoselecting item while typing (default Select behaviour)
                  e.stopPropagation();
                }
              }}
            />
          </ListSubheader>

          {displayedOptions.map((option, index) => (
            <MenuItem key={index} value={JSON.stringify(option)}>
              {option.label}
            </MenuItem>
          ))}
        </MuiSelect>
      </FormControl>
    </Box>
  );
};

export { SearchableSelect };

まとめ

  • 本家本元になかったので自前で実装しました
  • Material-UIはコンポーネントの組み合わせで色々できるのがいいですね!
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?