ããã«ã¡ã¯ãè·äººã§ãïŒ BOXIL SaaSãšã¯ æ°èŠã®ãã©ãŒã ãäœã React Hook Formãšã¯ãªããã åºæ¬çãªäœ¿ãæ¹ Recoilãšã¯ãªããã åºæ¬çãªäœ¿ãæ¹ Zodãšã¯ãªããã åºæ¬çãªäœ¿ãæ¹ React Hook Form & Recoil & Zod ãçµã¿åããããš èŠåŽããããš å
¥åããå€ãRecoilã®ã¹ããŒãã«ä¿åãããªã select boxã®onChangeãæ©èœããªã ä»å
¥åããŠããé
ç®ä»¥å€ã®é
ç®ã®ããªããŒã·ã§ã³ãå®è¡ãããŠããŸã 次åãžç¶ã ããã«ã¡ã¯ãè·äººã§ãïŒ ã¹ããŒããã£ã³ãã§BOXIL SaaSã®ãšã³ãžãã¢ããã£ãŠãŸãè·äººããšè¢Žç°ã§ãïŒ ä»åã¯æ°èŠäŒå¡ç»é²ã®ç»é¢ã«é¢ããŠUI/UXã®åäžã®ããã®æœçã察å¿ããããšã«ã€ããŠç޹ä»ããŸãã BOXIL SaaSãšã¯ BOXIL SaaSã¯SaaSãå°å
¥ããããŠãŒã¶ãŒãšSaaSãæäŸããŠãããã³ããŒãã€ãªããªãã³ã¢ãã«ã®ãããã¯ãã§ãã æ°èŠã®ãã©ãŒã ãäœã BOXIL SaaSã§ã¯äžéšReactã䜿çšããŠããŸãã React Hook Form Zod Recoil ä»åã¯ãã¡ãã®ã©ã€ãã©ãªãçµã¿åãããŠãã©ãŒã ãäœæããŸããã React Hook Formãšã¯ãªããã React Hook Form ãšã¯ãReactã§formãäœããšãã«äŸ¿å©ãªã©ã€ãã©ãªã§ãã åºæ¬çãªäœ¿ãæ¹ useFormãã register ãš handleSubmit ãååŸããããããinputã¿ã°ã®propsãšsubmitæã®åŠçã«æž¡ããŸãã registerã¯ã¹ãã¬ããæ§æã§å±éãããŠãonChangeãonBlurãªã©ã®ã€ãã³ããã³ãã©ãŒã«æž¡ãããŸãã // react-hook-formããuserFormãimport import { useForm } from "react-hook-form"; type FormValues = { firstName: string; lastName: string; }; function MyForm() { // useFormããregisterãšhandleSubmitãååŸ const { register, handleSubmit } = useForm<FormValues>(); // submitæã®åŠçãå®çŸ© const onSubmit = (data: FormValues) => console.log(data); return ( // onSubmitã«handleSubmitãæž¡ã <form onSubmit={handleSubmit(onSubmit)}> <input {...register("firstName")} /> <input {...register("lastName")} /> <input type="submit" /> </form> ); } Recoilãšã¯ãªããã Recoil ãšã¯ãReactã§ç¶æ
管çããããšãã«äŸ¿å©ãªã©ã€ãã©ãªã§ãã åºæ¬çãªäœ¿ãæ¹ 1.RecoilRootãèšå®ãã RecoilRootã䜿çšããŠãRecoilã®ç¶æ
ã管çããŸãã äžèšã®äŸã§ã¯Appã³ã³ããŒãã³ãã«å
æ¬ãããã³ã³ããŒãã³ãã§Recoilã®ç¶æ
ãååŸã§ããããã«ãªããŸãã ãããèšå®ãããŠããªããšãRecoilã®ç¶æ
ãååŸã§ããŸããã import React from "react"; import ReactDOM from "react-dom"; import { RecoilRoot } from "recoil"; import { BrowserRouter } from "react-router-dom"; import { App } from "./App"; ReactDOM.render( <React.StrictMode> <RecoilRoot> <BrowserRouter> <App /> </BrowserRouter> </RecoilRoot> </React.StrictMode>, document.getElementById("exampleApp") ); 2. atom ãå®çŸ©ãã atomã䜿çšããŠã管çãããç¶æ
ãåå¥ã«å®çŸ©ããŸãã atomãšã¯ã¢ããªã±ãŒã·ã§ã³ã®ç¶æ
ã管çããããã®åäœãšæã£ãŠããã ããã°ãããšæããŸãã import { atom } from 'recoil'; export const countState = atom({ key: 'countState', default: 0, }); 3. useRecoilState ã§ç¶æ
ãèªã¿æžããã useRecoilStateã䜿çšããŠãå€ãšã»ãã¿ãŒãååŸã§ããŸãã ã»ãã¿ãŒã䜿çšããå€ãæŽæ°ã§ããŸãã import { useRecoilState } from 'recoil'; import { countState } from './atoms'; function Counter() { // countãå€ // setCountãã»ãã¿ãŒ // ãšããã€ã¡ãŒãž const [count, setCount] = useRecoilState(countState); const increment = () => { // ã»ãã¿ãŒã䜿ã£ãŠãcountãæŽæ°ãã setCount(count + 1); }; return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> </div> ); } Zodãšã¯ãªããã zod ãšã¯ãReactã§ããªããŒã·ã§ã³ããããšãã«äŸ¿å©ãªã©ã€ãã©ãªã§ãã åºæ¬çãªäœ¿ãæ¹ z.objectã䜿çšããããªããŒã·ã§ã³å®è¡å¯Ÿè±¡ã®é
ç®ãèšå®ããã¹ããŒããå®çŸ©ããŸãã import { z } from "zod"; const schema = z.object({ email: z.string().email().max(10), name: z.string().max(10), }); type Data = z.infer<typeof schema>; const data: Data = { name: "tarou", age: 20 }; React Hook Form & Recoil & Zod ãçµã¿åããããš ããããçµã¿åããããã©ãŒã ã®äœæäŸãç°¡åã§ã¯ãããŸããã以äžã«ãŸãšããŸããã ãã©ãŒã ã®ç¶æ
ãããªããŒã·ã§ã³ãªã©ã¯ã«ã¹ã¿ã ããã¯(useUserForm.ts)ãšããŠãŸãšããŠãããMyFormåŽã§ã¯importããŠäœ¿çšããŠããŸãã useUserForm.ts import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { atom, useRecoilState } from "recoil"; import { z } from "zod"; const userSchema = z .object({ email: z.string().nonempty().email().max(10), name: z.string().nonempty().max(10), company: z.string().nonempty() }) type UserForm = z.infer<typeof userSchema>; const defaultValues: UserForm = { email: "", name: "", company: "" } const form = atom<UserForm>({ key: "useUserFormAtom", default: defaultValues, }); export const useUserForm = () => { const [formValues, setFormValues] = useRecoilState(form); const { register, handleSubmit, getValues, control, trigger, formState: { errors }, } = useForm({ resolver: zodResolver(userSchema), mode: "onSubmit", defaultValues: formValues, }); const handleSetFormValues = () => { console.log(JSON.stringify(getValues())) setFormValues(getValues()); }; return { handleSubmit, register, control, trigger, setFormValues, formValues, handleSetFormValues, errors }; }; MyForm.tsx import { useUserForm } from "./useUserForm"; import { Controller } from "react-hook-form"; import Select from "react-select"; export function MyForm() { const userForm = useUserForm(); const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); await userForm.trigger([ "email", "name", "company" ]); userForm.handleSetFormValues(); }; const OPTIONS = [ { label: "A瀟", value: "1", }, { label: "B瀟", value: "2", } ] return ( <form onSubmit={(e) => handleSubmit(e)}> {userForm.errors.email?.message && <p>{userForm.errors.email?.message}</p>} <input {...userForm.register("email")} /> {userForm.errors.name?.message && <p>{userForm.errors.name?.message}</p>} <input {...userForm.register("name")} /> {userForm.errors.company?.message && <p>{userForm.errors.company?.message}</p>} <Controller name="company" control={userForm.control} render={({ field }) => ( <Select options={OPTIONS} value={OPTIONS.find( (option) => option.value === field.value )} {...userForm.register("company")} onChange={async (e) => e != null ? field.onChange(e.value) : null} /> )} /> <input type="submit" /> </form> ); } èŠåŽããããš ããããã¯å®éã«ãããã¯ãã«èœãšã蟌ãéã«è©Šè¡é¯èª€ããããšãã玹ä»ããŸãã å
¥åããå€ãRecoilã®ã¹ããŒãã«ä¿åãããªã äŸãã°å
¥åãããã©ãŒã ã®å€ã次ã®ç»é¢ããã©ãŒã ã«ç§»åãããšããä¿æãããå Žé¢ãããããšæããŸãã ãããäœã工倫ããç»é¢é·ç§»ãããŠããŸããšããã©ãŒã ã«å
¥åããå€ã¯ä¿æã§ããŸããã Recoilã®ç¶æ
ã«ãä¿åãããŠããªãç¶æ
ã«ãªããŸãã Recoilã䜿çšããå Žåã¯ãã©ãŒã ã«å
¥åããå€ãRecoilã®ç¶æ
ã«ä¿åãããããã»ãã¿ãŒãå¿
ãåŒã°ãªããã°ãããŸããã const handleSetFormValues = () => { console.log(JSON.stringify(getValues())) // ããã§å
¥åããå€ãåºå setFormValues(getValues()); }; const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => { // çç¥ userForm.handleSetFormValues(); }; åè¿°ã®äŸã§ã¯onSubmitãçºç«ãããšãã«ãhandleSetFormValuesãçµç±ããŠsetFormValuesãåŒã³åºãRecoilã®ç¶æ
ã«ä¿åããŠããŸãã select boxã®onChangeãæ©èœããªã éžæåŒã®å
¥åé
ç®ã«ã¯ react-select ã䜿çšããŠããŸããã ãããReact Hook Formãšçµã¿åããããšãregisterã§å±éãããonChangeã¯æ©èœããªããªããŸãã react-selectã¯controledãªã³ã³ããŒãã³ãã§ãããReact Hook Formã®registerã¯uncontroledãªã³ã³ããŒãã³ãã«ãã察å¿ããŠãªãããã§ãã ãããè§£æ¶ããããã«React Hook Formã® Controller ãšããæ©èœã䜿çšããŸãã Controllerã§select boxãå²ããrenderã®äžã§react-selectã䜿çšãããšonChangeãæ©èœããããã«ãªããŸãã <Controller name="company" control={userForm.control} render={({ field }) => ( <Select options={OPTIONS} value={OPTIONS.find( (option) => option.value === field.value )} {...userForm.register("company")} onChange={async (e) => e != null ? field.onChange(e.value) : null} /> )} /> ä»å
¥åããŠããé
ç®ä»¥å€ã®é
ç®ã®ããªããŒã·ã§ã³ãå®è¡ãããŠããŸã React hook formã§ã¯ããªããŒã·ã§ã³ã®å®è¡ã¿ã€ãã³ã°ãæå®ã§ãã ã¢ãŒã ããããŸãã ããã¯onChange, onBlur, onSubmitãªã©ã®ã¢ãŒãããããããã©ã«ãã§ã¯onChangeã«ãªã£ãŠããŸãã onChangeã䜿çšããŠããå Žåã¯ãå
¥åããŠããé
ç®ã®å€ã倿ŽãããšããªããŒã·ã§ã³ãå®è¡ãããŸããããã©ãŒã ã«ååšããä»ã®å
¥åé
ç®ã«å¯ŸããŠãå®è¡ãããŠããŸãã ããã¯ä»çŸåšä»æ§ã®ããã§ãã®ã§ããããããŸãããã 解決çã¯onSubmitã«ããããšã§ãã const { register, handleSubmit, getValues, control, trigger, formState: { errors }, } = useForm({ resolver: zodResolver(userSchema), mode: "onSubmit", // ãããonSubmitã«ãã defaultValues: formValues, }); trigger ã䜿ã£ãŠä»»æã®ã¿ã€ãã³ã°ã§ããªããŒã·ã§ã³ãå®è¡ã§ããŸãã äœãã®ãã¿ã³ãã¯ãªãã¯ãããšãã«ããªããŒã·ã§ã³ãå®è¡ããå Žåã¯ä»¥äžã®ãããªå®è£
ã«ãªããŸãã <button type="button" onClick={() => { userForm.trigger([ "email", "name", "company" ]); }} > 次åãžç¶ã ä»åã¯React Hook FormãZodãRecoilãçµã¿åãããŠãã©ãŒã ãäœæãããšãã«èŠåŽããããšã玹ä»ããŸããã å®ã¯ä»å玹ä»ããããšä»¥å€ã«ããŸã èŠåŽããããšãããã€ããããŸãã æ¬¡ååŒãç¶ãã玹ä»ããããšæããŸãïŒ