## 針對 mui-datetime-picker 整合 react-hook-form、yup、ts (~~踩雷~~
由於我們使用`yup`關係~~早知道上TS就用Zod了~~,驗證`date`的型別只能辨識`new Date`,如果要使用`dayjs`則會有型別上衝突,但又不想為了`dayjs`去定義新的型別規則在`yup`上,使用`datetime-picker`時候又需要`dayjs`
* 所以邏輯上我們先在外層定義的`data`、`type`全都使用`new Date`格式
* 使用`datetime-picker`時,預設值則在用``dayjs``包起來注入
當交互資料改變時,再拋出 date格式資料回 form 裡面,形成一個健康的循環。
* 格式對了、驗證也掛起來了、資料也正常回傳了,全家都歡樂
package.json 版本
```json!
{
"dependencies": {
"@hookform/resolvers": "^3.3.2",
"@mui/icons-material": "^5.14.16",
"@mui/material": "^5.14.17",
"@mui/styled-engine": "npm:@mui/styled-engine-sc@latest",
"@mui/styles": "^5.16.4",
"@mui/x-date-pickers": "^7.10.0",
"dayjs": "^1.11.12",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.49.2",
"styled-components": "^6.1.1",
"vite": "^4.4.5",
"yup": "1.3.3"
},
"resolutions": {
"@mui/styled-engine": "npm:@mui/styled-engine-sc@latest"
},
"devDependencies": {
"typescript": "5.3.3",
}
}
```
### 1. 首先我們需要先定義出資料格式、型別、yup 驗證規則與其他相關配置
```javascript!
// type
type formType = {
formDate: Date;
};
// data
const defaultValues = {
formDate: new Date(),
// yup 日期格式只有針對 new Date,但使用 datetime picker 需要使用 dayjs,後面會說明
};
// yup rules
const formRules = yup.object({
formDate: yup.date().required().typeError('Please enter the correct date format.'),
});
// react-hook-form 建立
const {
handleSubmit,
control,
formState: { errors },
} = useForm<formType>({
resolver: yupResolver(formRules),
defaultValues,
mode: 'all'
});
// 驗證成功時觸發函式
const onSubmit = (data: formType) => {
console.log('data', data);
};
```
### 2. 要使用mui-datetime picker你則需要引入調用
> 這裡使用的是 Mui-X 系列功能 `x-date-pickers` 跟一般使用的稍微不一樣要注意安裝及引入
```javascript!
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
// ...code
<LocalizationProvider dateAdapter={AdapterDayjs}> //datetime-picker 的 Provider 層
<Controller
control={control}
name='formDate' // formrules 對照
render={({ field }) => (
<DateTimePicker
{...field}
label='Form Date'
value={dayjs(field.value)} // 為了配合使用時,資料格式需對應 dayjs
inputRef={field.ref}
slotProps={{
// props 注入個 textField
// 如果有需要額外設定紅框可添加 error: true 跟一般範例一樣
textField: {
helperText: errors.formDate?.message, // error msg 出現時機
},
}}
onChange={(date) => field.onChange(date)} // 送出修改值
/>
)}
/>
</LocalizationProvider>
// ...code
```
### 3. 最後的結果 Demo
```javascript!
import { yupResolver } from '@hookform/resolvers/yup';
import { Button, Card, CardActions, CardContent, CardHeader, Stack } from '@mui/material';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import dayjs from 'dayjs';
import { Controller, useForm } from 'react-hook-form';
import * as yup from 'yup';
const App = () => {
type formType = {
formDate: Date;
};
// data
const defaultValues = {
formDate: new Date(),
// yup 日期格式只有針對 new Date,但使用 datetime picker 需要使用 dayjs,後面會說明
};
// yup rules
const formRules = yup.object({
formDate: yup.date().required().typeError('Please enter the correct date format.'),
});
// react-hook-form 建立
const {
handleSubmit,
control,
formState: { errors },
} = useForm<formType>({
resolver: yupResolver(formRules),
defaultValues,
mode: 'all'
});
const onSubmit = (data: formType) => {
console.log('data', data);
};
return (
<Card>
<form onSubmit={handleSubmit(onSubmit)}>
<CardHeader title='mui- datetime picker、react-hook-form、yup 整合' />
<CardContent>
<Stack spacing={2}>
<LocalizationProvider dateAdapter={AdapterDayjs}> //datetime-picker 的 Provider 層
<Controller
control={control}
name='formDate' // formrules 對照
render={({ field }) => (
<DateTimePicker
{...field}
label='Form Date'
value={dayjs(field.value)} // 為了配合使用時,資料格式需對應 dayjs
inputRef={field.ref}
slotProps={{
// props 注入個 textField
// 如果有需要額外設定紅框可添加 error: true 跟一般範例一樣
textField: {
helperText: errors.formDate?.message, // error msg 出現時機
},
}}
onChange={(date) => field.onChange(date)} // 送出修改值
/>
)}
/>
</LocalizationProvider>
</Stack>
</CardContent>
<CardActions sx={{ justifyContent: 'center', paddingBottom: 2 }}>
<Button type='submit' variant='contained' sx={{ marginRight: 2 }}>
Submit
</Button>
</CardActions>
</form>
</Card>
);
};
export default App;
```
---
### 最後分享之前寫醜的時候有個很笨的做法
原則上基本的需求就是讓選擇日期時間,但之前參照網路上的寫法就是無法與`react-hook-form`規則順利全部整合在一起,所以這裡有個小密技是X)

* 先寫成基本調用使用方式
* `slotProps`裡的`textField`把它 disabled 這樣就能達到,防止他們自由輸入日期時間出錯
* user 只能點擊 input 旁的日期按鈕去選擇日期時間
* 間接達到需求也防止他們亂打,而且這樣也不需要 react-hook-form 去驗證
>但這後果就是,你的`formType`型別會與`react-hook-form`無法對齊狂噴紅字,需要做很多繞後門的事情,在經過痛定思痛還是決定一口氣整理好,ts也不抱錯全家抱頭痛哭。
Poo-Poo Code(非到萬不得已不要用啊
```javascript!
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DateTimePicker
label='Form Date'
value={dayjs(form.formDate)}
slotProps={{
textField: {
disabled: true,
},
}}
onChange={(date) => field.onChange(date)}
/>
</LocalizationProvider>
```