React Hooks (4) Form 表單操作

input 表單

1
2
3
4
5
6
7
8
const [inputState, setInputState] = useState('casper');

return (
<div>
<label htmlFor="username">使用者名稱 {inputState}</label>
<input id="username" type="text" value={inputState}
onChange={e => setInputState(e.target.value)}/>
</div>)

使用 input 表單時,

  1. 將 useState 的資料綁定到 value
  2. 偵聽 onChange 事件,將值用 setInputState 寫入 state。

select 選單

1
2
3
4
5
6
7
8
9
const optionsList = ['台北', '台中', '高雄'];
const [selectState, setSelectState] = useState('');

<select name="" id="" onChange={ e => setSelectState(e.target.value) }>
<option value="" disabled>-- 請選擇地區 --</option>
{
optionsList.map((city) => <option value={city} key={city}> { city } </option>)
}
</select>

使用 select 選單時:

  1. 定義 option 資料陣列
  2. 用 map 將 option 渲染到 select 內,並帶入 value
  3. 再 select 偵聽 onChange 事件,用 setSelectState 寫入資料

select 多選

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const optionsList = ['台北', '台中', '高雄'];
const [selectState, setSelectState] = useState([]);

<select name=""
value={selectState}
id=""
onChange={ e => {
const options = [...e.target.selectedOptions].map(option => option.value);
setSelectState(options);
}}
multiple={true}>
<option value="" disabled>-- 請選擇地區 --</option>
{
optionsList.map((city) => <option value={city} key={city}> { city } </option>)
}
</select>

使用 select 多選時:

  1. useState 要以空陣列定義
  2. select 監聽 onChange 事件時,資料會放在 e.target.selectedOptions,可用 map 將值取回

radio 單選題

1
2
3
4
5
6
7
8
const [check, setCheck] = useState(false);

<label htmlFor="isCheck">確認狀態 {check.toString()}</label>
<input id="isCheck" type="checkbox"
onChange={e => {
setCheck(e.target.checked);
}}
value={check}/>

radio 單選是用來了解用戶是否點擊,useState 預設可用 false。 用戶點擊後用 setCheck 將值寫入。

checkbox 複選

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const [checkList, setCheckList] = useState([]);
const hendleCheckList = (e) => {
if (e.target.checked) {
setCheckList([...checkList, e.target.value])
} else {
setCheckList(checkList.filter((item) => item !== e.target.value))
}
}

<label htmlFor="checkList1">炒麵</label>
<input id="checkList1" type="checkbox" name="like" onChange={hendleCheckList} value='炒麵' />
<label htmlFor="checkList2">鍋燒意麵</label>
<input id="checkList2" type="checkbox" name="like" onChange={hendleCheckList} value='鍋燒意麵' />
<label htmlFor="checkList3">炸薯條</label>
<input id="checkList3" type="checkbox" name="like" onChange={hendleCheckList} value='炸薯條' />

用 checkbox 複選時,一樣用陣列定義 state,onChange 偵聽的函式裡,要判斷此時用戶是勾選狀態嗎,是的話將 value 加入陣列,否的話則將 value 移除陣列。

表單送出

在 react 製作表單送出時:

  1. useState 設定要送出的表單物件
  2. 物件的屬性和 input 的 name 要一樣
  3. 每個 input 綁定 onChange 事件,用 hendleInputChange 來寫入 value 到物件
  4. 送出按鈕 type 為 submit, form 綁定 onSubmit 事件送出表單
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const App = () => {
const [formData, setFormData] = useState({
username: 'Casper',
password: '123456',
isCheck: false,
});

const hendleInputChange = (e) => {
const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
const name = e.target.name;
setFormData({
...formData,
[name]: value,
})
}

const handleSubmit = (e) => {
e.preventDefault();
}

return (<div>
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label htmlFor="username" className="form-label">使用者名稱</label
><input id="username" type="text" className="form-control" name="username"
onChange={hendleInputChange}
value={formData.username}/>
</div>
<div className="mb-3">
<label htmlFor="password" className="form-label">密碼</label
><input id="password" type="password" className="form-control" name="password"
onChange={hendleInputChange}
value={formData.password} />
</div>
<div className="mb-3">
<label htmlFor="isCheck">確認狀態</label>
<input id="isCheck" type="checkbox" name="isCheck"
onChange={hendleInputChange}
value={formData.isCheck}/>
</div>
<button type="submit" className="btn btn-primary">送出</button>
</form>
</div>)
}

常見表單監聽方法

1
2
3
4
5
6
7
8
<form onSubmit={formHandleSubmit}>
<div className="mb-3">
<label htmlFor="search2" className="form-label">搜尋</label>
<input id="search2" type="search" className="form-control" name="search"
onChange={(e) => setSearch(e.target.value)}
value={search} />
</div>
</form>

當 form 裡只有一個 input 時,按下 enter 能直接觸發表單送出,就算 input 的 type 不是 submit

1
2
3
4
5
6
7
8
9
10
11
12
const enterHandlerSubmit = (e) => {
// 按下 enter 才觸發
if (e.key === 'Enter') {
console.log('開始搜尋', search)
}
}
<label htmlFor="search1" className="form-label">搜尋</label>
{/* https://reactjs.org/docs/events.html#keyboard-events */}
<input id="search1" type="search" className="form-control" name="search"
value={search}
onChange={(e) => setSearch(e.target.value)}
onKeyPress={enterHandlerSubmit}

一般表單監聽可用 onChange 及時寫入值,用 onKeyPress 來送出表單

檔案上傳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const uploadFile = async (file) => {
const formData = new FormData();
formData.append('file-to-upload', file); // 表單欄位名稱,表單的值
try {
const res = await axios.post(`/v2/api/${apiPath}/admin/upload`, formData);
console.log(res.data.imageUrl);
} catch(error) {
alertError(error.response.data.message);
}
}

<div>
<p>或上傳檔案</p>
<input type="file" onChange={e => uploadFile(e.target.files[0])} />
</div>

在 react 裡要用 input 上傳檔案時:

  1. input type 選擇 file
  2. 偵聽 onChange 事件
  3. 檔案會在 e.target.files[0] 裡面
  4. 用 new FormData 建立表單物件
  5. formData 帶入欄位和值
  6. 將建立的表單物件發送到 api