Skip to content

Instantly share code, notes, and snippets.

@nandorojo
Created March 29, 2023 18:40
Show Gist options
  • Save nandorojo/889becac7ccf880500cb952d84ed80af to your computer and use it in GitHub Desktop.
Save nandorojo/889becac7ccf880500cb952d84ed80af to your computer and use it in GitHub Desktop.
React Hook Form TypeScript Wrapper
import {
FormProvider,
useForm,
useWatch,
useFormState,
useFormContext,
Path,
ControllerProps,
Controller,
UseFormProps,
UseFormReturn,
FormProviderProps,
useController,
DeepPartialSkipArrayKey,
UseControllerProps,
useFieldArray,
FieldArrayPath,
UseFieldArrayProps,
} from 'react-hook-form'
import { DevTool } from '@hookform/devtools'
import { Platform } from 'react-native'
const makeForm = <FormState extends object>() => {
const Provider = (props: FormProviderProps<FormState>) => {
return (
<FormProvider<FormState> {...props}>
{props.children}
{Platform.select({ web: <DevTool control={props.control} /> })}
</FormProvider>
)
}
return {
Controller: function CustomController<
Name extends Path<FormState> = Path<FormState>
>(props: ControllerProps<FormState, Name>) {
return <Controller<FormState, Name> {...props} />
},
useFieldArray: <
TFieldArrayName extends FieldArrayPath<FormState>,
TKeyName extends string = 'id'
>(
props: UseFieldArrayProps<FormState, TFieldArrayName, TKeyName>
) =>
useFieldArray<FormState, TFieldArrayName, TKeyName>({
control: useFormContext<FormState>().control,
...props,
}),
FormProvider: Provider,
useForm: (props: UseFormProps<FormState>) => {
const form = useForm<FormState>(props)
const handleDirtySubmit: UseFormReturn<
Partial<FormState>
>['handleSubmit'] = (onSubmit) => {
const {
// annoying hack. don't move this, it's in render to trigger the proxy to avoid stale values.
formState: { dirtyFields },
getValues,
} = form
return form.handleSubmit((_, e) => {
function getDirtyFields() {
const values = getValues()
const next = {} as Partial<FormState>
for (const key in dirtyFields) {
// @ts-expect-error
next[key] = values[key]
}
console.log('[get-dirty-fields]', dirtyFields)
return next
}
return onSubmit(getDirtyFields(), e)
})
}
return {
...form,
handleDirtySubmit,
}
},
useWatch: () =>
useWatch<FormState>({
control: useFormContext<FormState>().control,
}),
Watch({
render,
}: {
render: (props: DeepPartialSkipArrayKey<FormState>) => React.ReactElement
}) {
return <>{render(useWatch<FormState>())}</>
},
useFormContext: () => useFormContext<FormState>(),
useController: <Name extends Path<FormState> = Path<FormState>>(
props: Omit<UseControllerProps<FormState, Name>, 'control'>
) => {
return useController({
...props,
control: useFormContext<FormState>().control,
})
},
Submit({
children,
}: {
children: (
props: Pick<
ReturnType<typeof useFormState<FormState>>,
| 'isDirty'
| 'isValid'
| 'isSubmitting'
| 'isSubmitSuccessful'
| 'submitCount'
| 'errors'
> & {
hasErrored: boolean
}
) => React.ReactNode
}) {
const {
isDirty,
isValid,
isSubmitting,
isSubmitSuccessful,
submitCount,
errors,
} = useFormState<FormState>()
console.log('[submit]', { isValid, errors })
return (
<>
{children({
isDirty,
isValid: !Object.values(errors).length,
isSubmitting,
isSubmitSuccessful,
submitCount,
errors,
hasErrored: Object.values(errors).length > 0,
// && submitCount > 0,
})}
</>
)
},
}
}
export { makeForm }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment