Created
March 29, 2023 18:40
-
-
Save nandorojo/889becac7ccf880500cb952d84ed80af to your computer and use it in GitHub Desktop.
React Hook Form TypeScript Wrapper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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