Form
Form in React
2021/07/08 (改寫內容)
2021/10/13 (更新內容)
Form
在PHP(或傳統的web)裡,是透用Form以及http post,在submit後,送出Form裡的欄位內容給伺服器。在javascript裡,可以利用document.getElementById取得DOM的內容,再利用http post將資料送到伺服器。在介紹Firebase時,我們會介紹如何將資料送到伺服器。
在react裡,並不建議直接去取得DOM的內容,所以,寫法就跟javascript不一樣了。在react裡,則是在輸入時(onChange)就將內容放到state裡,並將value設為state。(詳參: Handling Events、Forms)
產品描述:<input type="text" name="desc" value={desc} onChange={handleClickDesc}/><br/>
產品價格:<input type="number" name="price" value={price} onChange={handleClickPrice}/><br/>
e是onChange預設傳給被呼叫方法的第一個參數,可以改為任何的變數名稱。可以利用console.log(e.target),就可以看到是取得DOM的元件,再從e.target中取得value,將value放到state裡。
handleClickDesc及handleClickPrice
function handleClickDesc(e) {
setDesc(e.target.value);
}
function handleClickPrice(e) {
setPrice(e.target.value);
}
在點擊按鈕時,就可以呼叫update函數。
<button onClick={update}>新增</button>
update函數可以傳兩個變數
const update = function(){
props.update(desc,price);
}
也可以將兩個變數包成一個物件
const update = function(){
props.update({desc,price});
}
包成物件的好處是如果增加第三個變數,接收端不用改參數。當我們程式很龐大,ProductAdd被多個元件使用是,這樣的作法,可以讓原有的元件還能使用,需要第三個變數的元件就在接收端加上變數即可。當然,也有人認為如果新增變數就應該改參數,否則會有意外。所以,就看使用的情境決定如何傳變數。
這個寫法,其實就是,
const update = function(){
props.update({desc:desc,price:price});
}
當物件屬性與對應變數名稱一樣,就可以省略。
update函數則呼叫ProductList裡的update函數。這樣的做法稱為callback method,如果只是簡單的父對子,可以使用這樣的方式,但是,如果元件的關係很多很複雜,這樣的做法就會產生所謂的call back hell,解決的方法有:context或者使用redux(或類似的方法)。
也可以將兩個useState合併
const [product, setProduct] = useState({desc:"",price:0})
handleClickDesc的部分要改成,也就是覆蓋掉desc的內容:
const handleClickDesc = function(e) {
setProduct({...product,desc:e.target.value});
}
update的部分,改成:
const update = function(){
props.update(product);
}
再把兩個handleClick整合起來,取得欄位的名稱:
const handleClick = function(e){
setProduct({...product,[e.target.name]:e.target.value})
}
完整程式
src/product/ProductAdd.js
import React, {useState} from 'react';
export default function ProductAdd(props) {
const [product, setProduct] = useState({desc:"",price:0})
const handleClick = function(e){
setProduct({...product,[e.target.name]:e.target.value})
}
const update = function(){
props.update(product);
}
return (
<div>
產品描述:<input type="text" name="desc" value={product.desc} onChange={handleClick}/><br/>
產品價格:<input type="number" name="price" value={product.price} onChange={handleClick}/><br/>
<button onClick={update}>新增</button>
</div>
);
}
在ProductList.js裡要有update函數來接收ProductAdd傳來的變數
setState (setProducts)接受兩種參數,第一種參數是個值,setState會更新state的內容,但是如果要利用原本的值,就會有問題。
3 Mistakes Junior Developers Make With React Function Component State
Modifying State Directly
Setting State That Relies on the Previous State Without Using a Function
Forgetting That the Setter Method from useState Is Asynchronous
所以,必須用第二種傳遞方式,就是接受一個callback函數,當參數是callback函數時,setState會將原有的值以及props傳給callback函數。 (詳參:State 和生命週期)
在下面範例裡,我們只使用原有的值,將這個值命名為oldProduct。在ProductList中,將ProductAdd傳回的newProduct,利用setProducts利用spread將收到的內容加到陣列最後。
spread語法請詳參: Day 09: ES6篇: Spread Operator & Rest Operator(展開與其餘運算符)
const insert = function (newProduct){
setProducts(oldProducts=>[...oldProducts, newProduct])
}
** 注意,這裡不能用push ,下面的語法是無法有效更新products **
直接push無法更動內容
const insert = function(newProduct){
products.push(newProduct);//not working....
}
也不能這樣更動內容
const insert = function(newProduct){
products.push(newProduct);
setProducts(products);//not working....
}
再把剛剛的ProductAdd加進來
<ProductAdd update={update}/>
src/product/ProductList.js
import ProductAdd from './ProductAdd'
import React, {useState} from 'react';
export default function ProductList() {
const [products, setProducts] = useState([
{desc:"iPad", price:20000},
{desc:"iPhone X", price:30000}
]);
const insert = function(newProduct){
setProducts(oldProducts=>[...oldProducts, newProduct]);
}
return (
<div>
<ul>
{products.map((product, index) => <li key={index}>{index} / {product.desc} / {product.price}</li>)}
</ul>
<ProductAdd update={insert}/>
</div>
);
}
Material-UI
Material-UI裡有很多元件可使用,例如,我們可以在ProductAdd裡使用Dialog、Input 、TextField及Button 。
Input
使用Input 的話,type可以使用html5的有效type (詳參: <input>: The Input (Form Input) element ),其他的props其實跟html是幾乎一樣的,唯一不同的是要用onChange,而不是onchange。
產品描述:<Input type="text" name="desc" value={desc} onChange={handleClickDesc}/><br/>
TextField
TextField 是由很多的元件組合,可以設定不同的樣式,會更像手機的介面。
<TextField label ="產品描述" variant="outlined" value={desc} onChange={handleClickDesc}/>
Button
使用Button時,可利用props設定樣式及顏色。
<Button variant="contained" color="primary" onClick={update}>新增</Button>
在ProductList裡可以使用Lists、Floating action button 。如果上週的作業還沒完成的,就請利用這機會完成。
Dialog
可以利用Dialog,來顯示內容,將Input及Button放在Dialog裡。
<Dialog>
產品描述:<Input type="text" name="desc" value={product.desc} onChange={handleClick}/><br/>
產品價格:<Input type="number" name="price" value={product.price} onChange={handleClick}/><br/>
<Button variant="contained" color="primary" onClick={update}>新增</Button>
</Dialog>
Dialog有一些props可以使用,open如果是true,就顯示dialog,如果是flase,就不顯示dialog。onClose則是處理關閉dialog的動作,在這裡可以把open設為false。
另外,可利用DialogTitle API 、DialogContent API 、DialogContentText API 、DialogActions API 來配置內容的位置。
<Dialog open={props.open} onClose={handleClose} aria-labelledby="新增產品">
<DialogTitle>"新增產品"</DialogTitle>
</Dialog>
Floating Action Button / FAB
可以利用Floating action button來啟動ProductAdd,想想看要怎麼改,才能顯示及不顯示dialog?
<Fab color="primary" aria-label="Add">
+
</Fab>
作業
將上面的範例套用Material-UI。
進階挑戰
如何刪除內容?
在ListItem裡增加ICON,讓使用者點選ICON就可以刪除內容
<IconButton edge="end" aria-label="delete">
<DeleteIcon />
</IconButton>
DeleteIcon
要安裝@mui/icons-material
npm install @mui/icons-material
要import
import DeleteIcon from '@mui/icons-material/Delete';
或者
import {Delete as DeleteIcon} from '@mui/icons-material';
在javascript裡,如何刪除陣列的內容? ** 記得,不能直接操作state變數! **
temp.splice(index,1);
如何修改內容?
在ListItem裡增加ICON,讓使用者點選ICON就可以修改內容
<IconButton edge="end" aria-label="edit">
<EditIcon />
</IconButton>
EditIcon
將點選內容放到Dialog裡,讓使用者可以修改,可以利用ProductAdd,改成也可以修改,也可以是兩個不同的元件
在javascript裡,如何修改陣列的內容? ** 記得,不能直接操作state變數! **
參考資料
How to Create Forms for Your React/React Native App
Controlled vs Uncontrolled Forms
How to choose a form library?
Popular Form Libraries
Formik -React and React Native
Formsy React -React and React Native
React Hook Form -React and React Native
Redux Forms -React and React Native