## 針對 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) ![image](https://hackmd.io/_uploads/B1gBiqztC.png) * 先寫成基本調用使用方式 * `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> ```