Skip to content

Commit

Permalink
ex: support for default value & useFieldArray
Browse files Browse the repository at this point in the history
  • Loading branch information
koichik committed Oct 29, 2022
1 parent 88f90e8 commit 4bb5e51
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 13 deletions.
84 changes: 77 additions & 7 deletions examples/react-hook-form/pages/form/[index].tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { object, string } from '@recoiljs/refine'
import { array, object, string } from '@recoiljs/refine'
import Head from 'next/head'
import { useRouter } from 'next/router'
import { SubmitHandler } from 'react-hook-form/dist/types/form'
Expand All @@ -7,11 +7,14 @@ import { initializableAtom } from 'recoil-sync-next'
import { useFormSync } from '../../src/hooks/useFormSync'
import styles from '../../styles/form.module.css'

import type { NextPage } from 'next'
import type { GetServerSideProps, NextPage } from 'next'

type FormState = {
name: string
comment: string
items: readonly {
value: string
}[]
}

const formState = initializableAtom<FormState>({
Expand All @@ -21,6 +24,7 @@ const formState = initializableAtom<FormState>({
refine: object({
name: string(),
comment: string(),
items: array(object({ value: string() })),
}),
}),
],
Expand All @@ -30,9 +34,26 @@ const Form: NextPage = () => {
// check render
console.log('Form: re render')

const { register, onChangeForm, handleSubmit } = useFormSync(
formState({ name: 'a', comment: 'b' })
const {
control,
registerWithDefaultValue,
onChangeForm,
handleSubmit,
reset,
useFieldArraySync,
} = useFormSync<FormState>(
formState({
name: 'a',
comment: 'b',
items: [{ value: '1' }, { value: '2' }],
})
)
const { fields, append, prepend, remove, swap, move, insert } =
useFieldArraySync({
control,
name: 'items',
})

const router = useRouter()
const onSubmit: SubmitHandler<FormState> = async (data) => {
console.log('submit data', data)
Expand All @@ -53,18 +74,67 @@ const Form: NextPage = () => {
<dl className={styles.formList}>
<dt>name</dt>
<dd>
<input type="text" {...register('name')} />
<input type="text" {...registerWithDefaultValue('name')} />
</dd>
<dt>comment</dt>
<dd>
<input type="text" {...register('comment')} />
<input type="text" {...registerWithDefaultValue('comment')} />
</dd>
<dt>items</dt>
<dd>
<button type="button" onClick={() => prepend({ value: '' })}>
先頭に追加
</button>
<ul>
{fields.map((field, index) => (
<li key={field.id}>
<span>{index} </span>
<input
{...registerWithDefaultValue(
`items.${index}.value` as const
)}
/>
<button type="button" onClick={() => move(index, 0)}>
先頭へ移動
</button>
<button type="button" onClick={() => remove(index)}>
削除
</button>
<button
type="button"
onClick={() => insert(index + 1, { value: '' })}
>
↓に挿入
</button>
<button
type="button"
disabled={index >= fields.length - 1}
onClick={() => swap(index, index + 1)}
>
↓と交換
</button>
</li>
))}
</ul>
<button type="button" onClick={() => append({ value: '' })}>
末尾に追加
</button>
</dd>
</dl>
<button>submit</button>
<button>Submit</button>
<button type="button" onClick={() => reset()}>
Reset
</button>
</form>
</main>
</div>
)
}

export default Form

export const getServerSideProps: GetServerSideProps = async ({ params }) => {
return {
props: {},
}
}
110 changes: 104 additions & 6 deletions examples/react-hook-form/src/hooks/useFormSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,23 @@ import { useCallback, useRef } from 'react'
import {
DeepPartial,
FieldValues,
InternalFieldName,
Resolver,
useFieldArray,
UseFieldArrayAppend,
UseFieldArrayInsert,
UseFieldArrayMove,
UseFieldArrayPrepend,
UseFieldArrayProps,
UseFieldArrayRemove,
UseFieldArrayReplace,
UseFieldArrayReturn,
UseFieldArraySwap,
UseFieldArrayUpdate,
useForm,
UseFormProps,
UseFormRegister,
UseFormRegisterReturn,
UseFormReturn,
} from 'react-hook-form'
import {
Expand All @@ -16,7 +31,15 @@ import {
type UseFormSyncReturn<
TFieldValues extends FieldValues,
TContext = any
> = UseFormReturn<TFieldValues, TContext> & { onChangeForm: () => void }
> = UseFormReturn<TFieldValues, TContext> & {
registerWithDefaultValue: UseFormRegister<TFieldValues>
onChangeForm: () => void
useFieldArraySync: (
props: UseFieldArrayProps<TFieldValues>
) => UseFieldArrayReturn<TFieldValues>
}

const FIELD_ARRAY_NAME_PATTERN = /([^.]+)\.([0-9]+)\.([^.]+)/

export function useFormSync<TFieldValues extends FieldValues, TContext = any>(
formState: RecoilState<TFieldValues>,
Expand All @@ -37,6 +60,7 @@ export function useFormSync<TFieldValues extends FieldValues, TContext = any>(
defaultValuesRef.current ??= getDefaultValues()

const {
register,
getValues,
reset: resetForm,
...rest
Expand All @@ -47,16 +71,90 @@ export function useFormSync<TFieldValues extends FieldValues, TContext = any>(
defaultValues: defaultValuesRef.current as DeepPartial<TFieldValues>,
})

const setFormValues = useSetRecoilState(formState)
const onChangeForm = useCallback(() => {
setFormValues(getValues())
}, [setFormValues, getValues])
const registerWithDefaultValue: UseFormRegister<TFieldValues> = useCallback(
(name, options) => {
const defaultValues = defaultValuesRef.current!
const names = FIELD_ARRAY_NAME_PATTERN.exec(name)
const defaultValue = names
? defaultValues[names[1]]?.[+names[2]]?.[names[3]]
: defaultValues[name]
return { ...register(name, options), defaultValue }
},
[register]
)

const resetState = useResetRecoilState(formState)
const reset = useCallback(() => {
resetState()
resetForm(getDefaultValues())
}, [getDefaultValues, resetForm, resetState])

return { ...rest, getValues, reset, onChangeForm }
const setFormValues = useSetRecoilState(formState)
const onChangeForm = useCallback(() => {
setFormValues(structuredClone(getValues()))
}, [setFormValues, getValues])

const useFieldArraySync = (
props: UseFieldArrayProps<TFieldValues>
): UseFieldArrayReturn<TFieldValues> => {
const origin = useFieldArray(props)
const swap: UseFieldArraySwap = (indexA, indexB) => {
origin.swap(indexA, indexB)
onChangeForm()
}
const move: UseFieldArrayMove = (indexA, indexB) => {
origin.move(indexA, indexB)
onChangeForm()
}
const prepend: UseFieldArrayPrepend<TFieldValues> = (value, options) => {
origin.prepend(value, options)
onChangeForm()
}
const append: UseFieldArrayAppend<TFieldValues> = (value, options) => {
origin.append(value, options)
onChangeForm()
}
const remove: UseFieldArrayRemove = (index) => {
origin.remove(index)
onChangeForm()
}
const insert: UseFieldArrayInsert<TFieldValues> = (
index,
value,
options
) => {
origin.insert(index, value, options)
onChangeForm()
}
const update: UseFieldArrayUpdate<TFieldValues> = (index, value) => {
origin.update(index, value)
onChangeForm()
}
const replace: UseFieldArrayReplace<TFieldValues> = (value) => {
origin.replace(value)
onChangeForm()
}

return {
swap,
move,
prepend,
append,
remove,
insert,
update,
replace,
fields: origin.fields,
}
}

return {
...rest,
register,
registerWithDefaultValue,
getValues,
reset,
onChangeForm,
useFieldArraySync,
}
}

0 comments on commit 4bb5e51

Please sign in to comment.