diff --git a/src/createSchemaForm.tsx b/src/createSchemaForm.tsx index fd5c853..55b3f74 100644 --- a/src/createSchemaForm.tsx +++ b/src/createSchemaForm.tsx @@ -141,6 +141,11 @@ function propsMapToObect(propsMap: PropsMapping) { return r; } +type RTFFormSchemaType = z.AnyZodObject | ZodEffects; +type RTFFormSubmitFn = ( + values: z.infer +) => void | Promise; + /** * Creates a reusable, typesafe form component based on a zod-component mapping. * @example @@ -227,9 +232,7 @@ export function createTsForm< const propsMap = propsMapToObect( options?.propsMap ? options.propsMap : defaultPropsMap ); - return function Component< - SchemaType extends z.AnyZodObject | ZodEffects - >({ + return function Component({ schema, onSubmit, props, @@ -247,7 +250,7 @@ export function createTsForm< /** * A callback function that will be called with the data once the form has been submitted and validated successfully. */ - onSubmit: (values: z.infer) => void | Promise; + onSubmit: RTFFormSubmitFn; /** * Initializes your form with default values. Is a deep partial, so all properties and nested properties are optional. */ @@ -363,42 +366,12 @@ export function createTsForm< const { control, handleSubmit, setError } = _form; const _schema = unwrapEffects(schema); const shape: Record = _schema._def.shape(); - const coerceUndefinedFieldsRef = useRef>(new Set()); - - function addToCoerceUndefined(fieldName: string) { - coerceUndefinedFieldsRef.current.add(fieldName); - } - - function removeFromCoerceUndefined(fieldName: string) { - coerceUndefinedFieldsRef.current.delete(fieldName); - } - - function removeUndefined(data: any) { - const r = { ...data }; - for (const undefinedField of coerceUndefinedFieldsRef.current) { - delete r[undefinedField]; - } - return r; - } - - function _submit(data: z.infer) { - return resolver(removeUndefined(data), {} as any, {} as any).then( - async (e) => { - const errorKeys = Object.keys(e.errors); - if (!errorKeys.length) { - await onSubmit(data); - return; - } - for (const key of errorKeys) { - setError( - key as any, - (e.errors as any)[key] as unknown as ErrorOption - ); - } - } - ); - } - const submitFn = handleSubmit(_submit); + const submitter = useSubmitter({ + resolver, + onSubmit, + setError, + }); + const submitFn = handleSubmit(submitter.submit); type SchemaKey = keyof z.infer>; const renderedFields = Object.keys(shape).reduce( (accum, key: SchemaKey) => { @@ -442,8 +415,8 @@ export function createTsForm< label={ctxLabel} placeholder={ctxPlaceholder} enumValues={meta.enumValues as string[] | undefined} - addToCoerceUndefined={addToCoerceUndefined} - removeFromCoerceUndefined={removeFromCoerceUndefined} + addToCoerceUndefined={submitter.addToCoerceUndefined} + removeFromCoerceUndefined={submitter.removeFromCoerceUndefined} > @@ -470,3 +443,58 @@ export function createTsForm< ); }; } +// handles internal custom submit logic +// Implements a workaround to allow devs to set form values to undefined (as it breaks react hook form) +// For example https://github.com/react-hook-form/react-hook-form/discussions/2797 +function useSubmitter({ + resolver, + onSubmit, + setError, +}: { + resolver: ReturnType; + onSubmit: RTFFormSubmitFn; + setError: ReturnType["setError"]; +}) { + const coerceUndefinedFieldsRef = useRef>(new Set()); + + function addToCoerceUndefined(fieldName: string) { + coerceUndefinedFieldsRef.current.add(fieldName); + } + + function removeFromCoerceUndefined(fieldName: string) { + coerceUndefinedFieldsRef.current.delete(fieldName); + } + + function removeUndefined(data: any) { + const r = { ...data }; + for (const undefinedField of coerceUndefinedFieldsRef.current) { + delete r[undefinedField]; + } + return r; + } + + function submit(data: z.infer) { + return resolver(removeUndefined(data), {} as any, {} as any).then( + async (e) => { + const errorKeys = Object.keys(e.errors); + if (!errorKeys.length) { + await onSubmit(data); + return; + } + for (const key of errorKeys) { + setError( + key as any, + (e.errors as any)[key] as unknown as ErrorOption + ); + } + } + ); + } + + return { + submit, + removeUndefined, + removeFromCoerceUndefined, + addToCoerceUndefined, + }; +}