무엇
<select>는 브라우저의 선택 상자(드롭다운)를 렌더한다. 자식으로 놓은 <option>들 중 하나, 또는 여러 개를 사용자가 고른다.
<option>, <optgroup>을 자식으로 받는다. 각 <option>에는 value가 있어야 한다.
<select>
<option value="apple">사과</option>
<option value="orange">오렌지</option>
</select>
무슨 일이 일어나나
핵심은 선택 상태를 누가 쥐느냐다. React에서는 HTML의 <option selected>를 쓰지 않는다. 대신 <select> 자체에 value 또는 defaultValue를 준다.
defaultValue만 주면 **비제어(uncontrolled)**다. 초기 선택만 정하고, 이후 선택은 브라우저(DOM)가 관리한다. value를 주면 **제어(controlled)**가 된다. React가 값을 강제하기 때문에, 사용자가 다른 항목을 골라도 상태를 바꿔 주지 않으면 화면은 원래 값으로 되돌아간다. 그래서 제어 컴포넌트에는 onChange가 사실상 필수다.
한 <select>가 살아 있는 동안 제어와 비제어를 오갈 수 없다. value와 defaultValue를 동시에 주는 것도 안 된다. 둘 중 하나로 정하고 유지한다.
multiple={true}면 여러 개를 고를 수 있고, 이때 value/defaultValue는 문자열이 아니라 배열이다.
사용법
비제어는 defaultValue로 초기 선택만 정한다. 폼 제출로 값을 받을 때 흔히 쓴다.
export function FruitPicker() {
return (
<select name="fruit" defaultValue="orange">
<option value="apple">사과</option>
<option value="orange">오렌지</option>
<option value="banana">바나나</option>
</select>
)
}
제어는 value와 onChange를 짝지어, 선택할 때마다 상태를 갱신한다. 선택에 따라 다른 UI를 다시 그려야 할 때 쓴다.
import { useState } from 'react'
export function FruitPicker() {
const [fruit, setFruit] = useState('orange')
return (
<select value={fruit} onChange={(e) => setFruit(e.target.value)}>
<option value="apple">사과</option>
<option value="orange">오렌지</option>
<option value="banana">바나나</option>
</select>
)
}
여러 개 선택은 multiple을 켜고 배열 상태를 쓴다. 고른 항목은 e.target.selectedOptions로 읽는다.
const [vegs, setVegs] = useState<string[]>(['corn'])
<select
multiple
value={vegs}
onChange={(e) => {
const picked = [...e.target.selectedOptions].map((o) => o.value)
setVegs(picked)
}}
>
<option value="corn">옥수수</option>
<option value="tomato">토마토</option>
</select>
실무 예시
폼 안에서는 비제어로 두고 제출 때 FormData로 한 번에 읽는 편이 간단하다. name이 폼 데이터의 키가 된다.
export function EditPost() {
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
const formData = new FormData(e.currentTarget)
console.log(Object.fromEntries(formData)) // { fruit: "orange" }
}
return (
<form onSubmit={handleSubmit}>
<select name="fruit" defaultValue="orange">
<option value="apple">사과</option>
<option value="orange">오렌지</option>
</select>
<button type="submit">저장</button>
</form>
)
}
왜 중요한가
선택 상자는 거의 모든 폼에 등장하고, 제어와 비제어를 가르는 규칙은 <input>, <textarea>와 그대로 겹친다. React에서 <option selected>가 아니라 부모 <select>의 value/defaultValue로 선택을 다룬다는 점 하나가 자주 걸리는 대목이다. 이 규칙에 익숙해지면 폼 요소 전반을 같은 방식으로 다룰 수 있다.
Reference