본문 바로가기
Develop

[React + Typescript] SelectBox Component는 어떻게 만드는게 좋을까?

by stuckyi 2024. 6. 16.

 
사이드 프로젝트를 시작할 때 당장 정해진 디자인이 없다면 빠르게 시작할 수 있는 MUI와 같은 컴포넌트 라이브러리를 선택하는 것이 좋다. 실무에선 디자인 시스템이 있는 회사라면 딱히 고민 없이 FE 개발을 시작할 것이다.
 
위와 같은 경우가 아니라면 나와 같이 직접 컴포넌트를 처음부터 만들어야 할 것이다. 그런데 막상 '어떻게 시작하지?', '어떻게 재사용할 수 있는 컴포넌트를 만들지?'와 같은 고민 속에 빠져 쉽사리 코드를 치지 못하고 시간만 보낼 수도 있다. 우선 나는 만들고 고치고 만들고 고쳐보자란 마음으로 시작을 하였고 그 첫 기록을 남겨보려고 한다. 여러 가지 컴포넌트 중 SelectBox를 첫 번째로 만들어보았다.
 

1. SelectBox 기본 디자인 요소는 뭐가 있을까

우선 기본 디자인으로 아래와 같은 케이스들을 생각해 볼 수 있었다.

  • label이 있거나 없거나
  • placeholder가 있거나 없거나
  • key와 value로 구성된 options
  • validate의 error시 표시(ex. 메시지, 글씨 색상, selectbox 배경색 또는 테두리색)
  • option을 선택했을 때 선택된 값, 선택된 표시(ex. 글씨 색상, selectbox 배경색 또는 테두리색)
  • disabled

출처. MUI Select 참고

 

2. 마크업 부터 해볼까

<form>
    <label for="age-select">Age</label>
    <select id="age-select" name="age">
        <option value="">나이</option>
        <option value="10">10대</option>
        <option value="20">20대</option>
        <option value="30">30대</option>
        <option value="40">40대</option>
    </select>
    <p>나이를 선택해주세요</p>
</form>

 
참고로 MUI Select 예시 코드를 보면 아래와 같이 구현이 되어 있다.

<FormControl variant="filled" className={classes.formControl}>
  <InputLabel id="demo-simple-select-filled-label">Age</InputLabel>
  <Select
    labelId="demo-simple-select-filled-label"
    id="demo-simple-select-filled"
    value={age}
    onChange={handleChange}
  >
    <MenuItem value="">
      <em>None</em>
    </MenuItem>
    <MenuItem value={10}>Ten</MenuItem>
    <MenuItem value={20}>Twenty</MenuItem>
    <MenuItem value={30}>Thirty</MenuItem>
  </Select>
</FormControl>

 

3. type 설정을 해보자

디자인과 마크업 구조를 보면, 필요한 값을 대략적으로 나열할 수 있게 되었다.
다만 typescript를 쓰면서 타입 정의에 대해 꼼꼼하게 미리 정하고, 맞춰서 쓰는 걸 습관화해야 한다는 걸 느끼고 있는 요즘이다. 
우선 나는 label이나 placeholder는 있거나 없거나 조건을 두었기 때문에 optional로 두었고
options는 배열, selectedValue는 string, error는 메시지를 보여줄 것이므로 string, disabled는 true/false로, 마지막으로 handleChange 함수타입으로 정의해 두었다.
 

interface ISelectBoxProps {
    label?: string
    options: [];
    selectedValue: string;
    placeholder?: string;
    error: string;
    disabled: boolean;
    handleChange: (e: ChangeEvent<HTMLSelectElement>) => void;
}

 

4. 컴포넌트를 만들어보자

프로젝트 구조에서 src> components > SelectBox.tsx 생성하였다. 그리고 tailwind css를 사용했다.

interface IOption {
  key: number | string;
  value: string;
}

interface ISelectBoxProps {
    label?: string
    options: [];
    selectedValue: string;
    placeholder?: string;
    error: string;
    disabled: boolean;
    handleChange: (e: ChangeEvent<HTMLSelectElement>) => void;
}

export default function SelectBox({
  label,
  options,
  selectedValue,
  placeholder,
  error,
  disabled,
  handleChange,
}: ISelectBoxProps) {
  return (
    <div className="relative">
      {label && <label className="text-xs">{label}</label>}
      <div>
        <select
          className="h-12 w-full rounded-lg bg-none px-3"
          value={selectedValue}
          onChange={(e) => handleChange(e)}
          disabled={disabled}
        >
          <option value="" className="">
            {placeholder}
          </option>
          {options.map((option) => (
            <option
              key={`select_${option.key}`}
              value={option.key}
              data-name={option.value}
            >
              {option.value}
            </option>
          ))}
        </select>
      </div>
      {error && (
        <p className="absolute mt-0.5 text-xs text-[#F44336]">{error}</p>
      )}
    </div>
  );
}

 
그럼 SelectBox 호출부에서는 아래와 같이 사용하면 된다.

import SelectBox from "./SelectBox";

import { useState } from "react";

export default function PageWrapper() {
  const [selectValue, setSelectValue] = useState<IUniversity>("");

  const handleSelectChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
	setSelectValue(event.target.value)
  };
  return (
    <form>
      <SelectBox
        label="Age"
        options={[]}
        selectedValue={selectValue}        
        placeholder="나이"
        error="나이를 선택해주세요"
        disabled={false}
        handleChange={handleSelectChange}
      ></SelectBox>
    </form>
  );
}

 
 

마무리

SelectBox를 어떻게 만들고 관리할지 정리를 해보았다. 위 내용은 혼자 개발하는 입장이다 보니 모든 선택 기준이 작업자인 나의 마음대로 진행이 되었는데 실무에선 조금 더 깊게 고민을 해봐야 할 것이다. 
예를 들어 호출하는 API에 따라 타입이 달라질 수 있기 때문에 BE와 API 논의할 때 타입을 잘 체크해 봐야 할 것 같고,
SelectBox를 여러개 관리하게 될 수 있고, 
SelectBox의 선택값에 따라 조건이 달라지는 비즈니스 로직이 생길 수 있는 등 다양한 케이스들을 마주하게 될 것 같다.