Skip to content

Commit 284c30a

Browse files
authored
Merge pull request #41 from DouglasNeuroInformatics/suspendOnSubmitting
feat: add suspendOnSubmitting
2 parents 46ddbf3 + 9172867 commit 284c30a

File tree

1 file changed

+42
-9
lines changed

1 file changed

+42
-9
lines changed

src/components/Form/Form.tsx

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { Promisable } from 'type-fest';
1313
import { z } from 'zod';
1414

1515
import { useTranslation } from '@/hooks';
16+
import { cn } from '@/utils';
1617

1718
import { Button } from '../Button';
1819
import { Heading } from '../Heading';
@@ -40,6 +41,7 @@ type FormProps<TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<T
4041
resetBtn?: boolean;
4142
revalidateOnBlur?: boolean;
4243
submitBtnLabel?: string;
44+
suspendWhileSubmitting?: boolean;
4345
validationSchema: z.ZodType<TData>;
4446
};
4547

@@ -58,6 +60,7 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
5860
resetBtn,
5961
revalidateOnBlur,
6062
submitBtnLabel,
63+
suspendWhileSubmitting,
6164
validationSchema,
6265
...props
6366
}: FormProps<TSchema, TData>) => {
@@ -67,6 +70,7 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
6770
const [values, setValues] = useState<PartialFormDataType<TData>>(
6871
initialValues ? getInitialValues(initialValues) : {}
6972
);
73+
const [isSubmitting, setIsSubmitting] = useState(false);
7074

7175
const handleError = (error: z.ZodError<TData>) => {
7276
const fieldErrors: FormErrors<TData> = {};
@@ -97,14 +101,21 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
97101
};
98102

99103
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
100-
event.preventDefault();
101-
const result = await validationSchema.safeParseAsync(values);
102-
if (result.success) {
103-
reset();
104-
await onSubmit(result.data);
105-
} else {
106-
console.error(result.error.issues);
107-
handleError(result.error);
104+
const minSubmitTime = new Promise((resolve) => setTimeout(resolve, 500));
105+
try {
106+
setIsSubmitting(true);
107+
event.preventDefault();
108+
const result = await validationSchema.safeParseAsync(values);
109+
if (result.success) {
110+
reset();
111+
await onSubmit(result.data);
112+
} else {
113+
console.error(result.error.issues);
114+
handleError(result.error);
115+
}
116+
} finally {
117+
await minSubmitTime;
118+
setIsSubmitting(false);
108119
}
109120
};
110121

@@ -128,6 +139,8 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
128139
revalidate();
129140
}, [resolvedLanguage]);
130141

142+
const isSuspended = Boolean(suspendWhileSubmitting && isSubmitting);
143+
131144
return (
132145
<form
133146
autoComplete="off"
@@ -176,8 +189,28 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
176189
<div className="flex w-full gap-3">
177190
{additionalButtons?.left}
178191
{/** Note - aria-label is used for testing in downstream packages */}
179-
<Button aria-label="Submit" className="block w-full" disabled={readOnly} type="submit" variant="primary">
192+
<Button
193+
aria-label="Submit"
194+
className="flex w-32 items-center justify-center gap-2"
195+
disabled={readOnly || isSuspended}
196+
type="submit"
197+
variant="primary"
198+
>
180199
{submitBtnLabel ?? t('form.submit')}
200+
<svg
201+
className={cn('hidden h-4 w-4 animate-spin', isSuspended && 'block')}
202+
fill="none"
203+
height="24"
204+
stroke="currentColor"
205+
strokeLinecap="round"
206+
strokeLinejoin="round"
207+
strokeWidth="2"
208+
viewBox="0 0 24 24"
209+
width="24"
210+
xmlns="http://www.w3.org/2000/svg"
211+
>
212+
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
213+
</svg>
181214
</Button>
182215
{resetBtn && (
183216
<Button

0 commit comments

Comments
 (0)