Skip to content

Commit 67710bf

Browse files
committed
feat: add beforeSubmit callback to form
1 parent a03d6ce commit 67710bf

File tree

3 files changed

+43
-4
lines changed

3 files changed

+43
-4
lines changed

src/components/Form/ErrorMessage.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import * as React from 'react';
22

33
import { CircleAlertIcon } from 'lucide-react';
44

5-
export const ErrorMessage: React.FC<{ error?: null | string[] }> = ({ error }) => {
5+
import { cn } from '@/utils';
6+
7+
export const ErrorMessage: React.FC<{ className?: string; error?: null | string[] }> = ({ className, error }) => {
68
return error ? (
79
<div className="space-y-1.5">
810
{error.map((message) => (
9-
<div className="flex w-full items-center text-sm font-medium text-destructive" key={message}>
11+
<div className={cn('text-destructive flex w-full items-center text-sm font-medium', className)} key={message}>
1012
<CircleAlertIcon className="mr-1" style={{ strokeWidth: '2px' }} />
1113
<span>{message}</span>
1214
</div>

src/components/Form/Form.stories.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,3 +530,27 @@ export const WithSuspend: StoryObj<typeof Form<z.ZodType<FormTypes.Data>, { dela
530530
})
531531
}
532532
};
533+
534+
export const WithError: StoryObj<typeof Form> = {
535+
args: {
536+
content: {
537+
name: {
538+
kind: 'string',
539+
label: 'Name',
540+
variant: 'input'
541+
}
542+
},
543+
beforeSubmit: (data) => {
544+
if (data.name === 'Winston') {
545+
return { success: true };
546+
}
547+
return { success: false, errorMessage: "Name must be 'Winston'" };
548+
},
549+
onSubmit: () => {
550+
alert('Success!');
551+
},
552+
validationSchema: $SimpleExampleFormData.extend({
553+
name: z.string().min(3)
554+
})
555+
}
556+
};

src/components/Form/Form.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type FormProps<TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<T
2929
left?: React.ReactNode;
3030
right?: React.ReactNode;
3131
};
32+
beforeSubmit?: (data: NoInfer<TData>) => Promisable<{ errorMessage: string; success: false } | { success: true }>;
3233
className?: string;
3334
content: FormContent<TData>;
3435
customStyles?: {
@@ -51,6 +52,7 @@ type FormProps<TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<T
5152

5253
const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TSchema> = z.TypeOf<TSchema>>({
5354
additionalButtons,
55+
beforeSubmit,
5456
className,
5557
content,
5658
customStyles,
@@ -78,6 +80,7 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
7880

7981
const handleError = (error: z.ZodError<TData>) => {
8082
const fieldErrors: FormErrors<TData> = {};
83+
const rootErrors: string[] = [];
8184
for (const issue of error.issues) {
8285
if (issue.path.length > 0) {
8386
const current = get(fieldErrors, issue.path) as string[] | undefined;
@@ -87,10 +90,11 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
8790
set(fieldErrors, issue.path, [issue.message]);
8891
}
8992
} else {
90-
setRootErrors((prevErrors) => [...prevErrors, issue.message]);
93+
rootErrors.push(issue.message);
9194
}
9295
}
9396
setErrors(fieldErrors);
97+
setRootErrors(rootErrors);
9498
if (onError) {
9599
onError(error);
96100
}
@@ -112,6 +116,15 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
112116
handleError(result.error);
113117
return;
114118
}
119+
if (beforeSubmit) {
120+
const beforeSubmitResult = await beforeSubmit(result.data);
121+
if (!beforeSubmitResult.success) {
122+
setErrors({});
123+
setRootErrors([beforeSubmitResult.errorMessage]);
124+
return;
125+
}
126+
}
127+
115128
try {
116129
setIsSubmitting(true);
117130
await Promise.all([
@@ -193,6 +206,7 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
193206
values={values}
194207
/>
195208
)}
209+
{Boolean(rootErrors.length) && <ErrorMessage className="-mt-3" error={rootErrors} />}
196210
{fieldsFooter}
197211
<div className="flex w-full gap-3">
198212
{additionalButtons?.left}
@@ -234,7 +248,6 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
234248
)}
235249
{additionalButtons?.right}
236250
</div>
237-
{Boolean(rootErrors.length) && <ErrorMessage error={rootErrors} />}
238251
</form>
239252
);
240253
};

0 commit comments

Comments
 (0)