Skip to content

Commit 0ea0d62

Browse files
authored
Merge pull request #24 from wisemen-digital/feature/on-submit-error
feature: on submit error
2 parents 1d81f88 + d2c143c commit 0ea0d62

File tree

3 files changed

+146
-8
lines changed

3 files changed

+146
-8
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "formango",
33
"type": "module",
4-
"version": "2.0.28",
4+
"version": "2.0.30",
55
"packageManager": "[email protected]",
66
"description": "",
77
"author": "Wouter Laermans <[email protected]>",

src/lib/useForm.ts

+34-7
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,25 @@ interface UseFormReturnType<TSchema extends z.ZodType> {
1212
* @param data The current form data.
1313
*/
1414
onSubmitForm: (cb: (data: z.infer<TSchema>) => void) => void
15+
/**
16+
* Called when the form is attempted to be submitted, but is invalid.
17+
* Only called for client-side validation.
18+
*/
19+
onSubmitFormError: (cb: () => void) => void
1520
/**
1621
* The form instance itself.
1722
*/
1823
form: Form<TSchema>
1924
}
2025

2126
interface UseFormOptions<TSchema extends z.ZodType> {
27+
/**
28+
* The zod schema of the form.
29+
*/
2230
schema: TSchema
31+
/**
32+
* The initial state of the form
33+
*/
2334
initialState?: MaybeRefOrGetter<NullableKeys<z.infer<TSchema>>>
2435
}
2536

@@ -41,6 +52,7 @@ export function useForm<TSchema extends z.ZodType>(
4152
const errors = ref<z.ZodFormattedError<TSchema>>({} as z.ZodFormattedError<TSchema>)
4253

4354
let onSubmitCb: ((data: z.infer<TSchema>) => MaybePromise<void>) | null = null
55+
let onSubmitFormErrorCb: (() => void) | undefined
4456

4557
const isSubmitting = ref<boolean>(false)
4658
const hasAttemptedToSubmit = ref<boolean>(false)
@@ -422,10 +434,18 @@ export function useForm<TSchema extends z.ZodType>(
422434

423435
// Check if the field is already registered
424436
if (existingId !== null) {
425-
const field = registeredFields.get(existingId) ?? null
437+
let field = registeredFields.get(existingId) ?? null
426438

427-
if (field === null)
428-
throw new Error(`${path} is already registered as a field array`)
439+
if (field === null) {
440+
const value = get(form, path)
441+
field = createField(existingId, path, value)
442+
}
443+
444+
// Check if value of the field is null or empty array, if so, set default value
445+
const isEmpty = field.modelValue === null || (Array.isArray(field.modelValue) && field.modelValue.length === 0)
446+
447+
if (isEmpty && defaultValue !== undefined)
448+
field.setValue(clonedDefaultValue)
429449

430450
// If it is, check if it is still being tracked
431451
const existingTrackedDependency = getIsTrackedbyId(trackedDepencies, existingId)
@@ -473,10 +493,12 @@ export function useForm<TSchema extends z.ZodType>(
473493
// Check if the field is already registered
474494
if (existingId !== null) {
475495
// Check if it is registered as a field array
476-
const fieldArray = registeredFieldArrays.get(existingId) ?? null
496+
let fieldArray = registeredFieldArrays.get(existingId) ?? null
477497

478-
if (fieldArray === null)
479-
throw new Error(`${path} is already registered as a field`)
498+
if (fieldArray === null) {
499+
const value = get(form, path)
500+
fieldArray = createFieldArray(existingId, path, value ?? [])
501+
}
480502

481503
// Check if it is still being tracked
482504
const existingTrackedDependency = getIsTrackedbyId(trackedDepencies, existingId)
@@ -554,8 +576,10 @@ export function useForm<TSchema extends z.ZodType>(
554576

555577
blurAll()
556578

557-
if (!isValid.value)
579+
if (!isValid.value) {
580+
onSubmitFormErrorCb?.()
558581
return
582+
}
559583

560584
// We need to keep track of the current form state, because the form might change while submitting
561585
const currentFormState = deepClone(form)
@@ -642,5 +666,8 @@ export function useForm<TSchema extends z.ZodType>(
642666
onSubmitForm: (cb: (data: z.infer<TSchema>) => MaybePromise<void>) => {
643667
onSubmitCb = cb
644668
},
669+
onSubmitFormError: (cb: () => void) => {
670+
onSubmitFormErrorCb = cb
671+
},
645672
}
646673
}

test/useForm.test.ts

+111
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ const basicArraySchema = z.object({
2020
array: z.array(z.string()),
2121
})
2222

23+
const basic2DArraySchema = z.object({
24+
array: z.array(z.array(z.string())),
25+
})
26+
2327
const objectArraySchema = z.object({
2428
array: z.array(
2529
z.object({
@@ -234,6 +238,92 @@ describe('useForm', () => {
234238
],
235239
})
236240
})
241+
242+
it('should register a field as a fieldArray', () => {
243+
const { form } = useForm({
244+
schema: basicArraySchema,
245+
})
246+
247+
const arrayAsField = form.register('array', ['John'])
248+
const arrayAsArray = form.registerArray('array')
249+
250+
expect(form.state).toEqual({
251+
array: ['John'],
252+
})
253+
254+
expect(arrayAsField.modelValue).toEqual(['John'])
255+
expect(arrayAsArray.modelValue).toEqual(['John'])
256+
257+
arrayAsField.setValue(['Doe'])
258+
259+
expect(form.state).toEqual({
260+
array: ['Doe'],
261+
})
262+
263+
expect(arrayAsField.modelValue).toEqual(['Doe'])
264+
expect(arrayAsArray.modelValue).toEqual(['Doe'])
265+
266+
arrayAsArray.append('John')
267+
268+
expect(form.state).toEqual({
269+
array: ['Doe', 'John'],
270+
})
271+
272+
expect(arrayAsField.modelValue).toEqual(['Doe', 'John'])
273+
expect(arrayAsArray.modelValue).toEqual(['Doe', 'John'])
274+
})
275+
276+
it('should register a fieldArray as a field', () => {
277+
const { form } = useForm({
278+
schema: basicArraySchema,
279+
})
280+
281+
const arrayAsArray = form.registerArray('array', ['John'])
282+
const arrayAsField = form.register('array')
283+
284+
expect(form.state).toEqual({
285+
array: ['John'],
286+
})
287+
288+
expect(arrayAsField.modelValue).toEqual(['John'])
289+
expect(arrayAsArray.modelValue).toEqual(['John'])
290+
291+
arrayAsField.setValue(['Doe'])
292+
293+
expect(form.state).toEqual({
294+
array: ['Doe'],
295+
})
296+
297+
expect(arrayAsField.modelValue).toEqual(['Doe'])
298+
expect(arrayAsArray.modelValue).toEqual(['Doe'])
299+
300+
arrayAsArray.append('John')
301+
302+
expect(form.state).toEqual({
303+
array: ['Doe', 'John'],
304+
})
305+
306+
expect(arrayAsField.modelValue).toEqual(['Doe', 'John'])
307+
expect(arrayAsArray.modelValue).toEqual(['Doe', 'John'])
308+
})
309+
310+
it('should register a 2D array field with a default value', () => {
311+
const { form } = useForm({
312+
schema: basic2DArraySchema,
313+
})
314+
315+
const array = form.registerArray('array', [[]])
316+
317+
expect(array.modelValue).toEqual([[]])
318+
319+
expect(form.state).toEqual({
320+
array: [[]],
321+
})
322+
323+
array.register('0', ['John'])
324+
325+
expect(array.modelValue).toEqual([['John']])
326+
})
237327
})
238328

239329
describe('unregister a field or fieldArray', () => {
@@ -436,6 +526,8 @@ describe('useForm', () => {
436526

437527
const array = form.registerArray('array')
438528

529+
array.register('0')
530+
439531
array.remove(1)
440532

441533
expect(form.state).toEqual({
@@ -873,6 +965,25 @@ describe('useForm', () => {
873965

874966
expect(submitted).toEqual(false)
875967
})
968+
969+
it('should call `onSubmitError` if there are errors', async () => {
970+
let isCalled = false
971+
972+
const { form, onSubmitFormError } = useForm({
973+
schema: basicSchema,
974+
})
975+
976+
onSubmitFormError(() => {
977+
isCalled = true
978+
})
979+
980+
form.register('name', 'Jon')
981+
982+
await sleep(0)
983+
await form.submit()
984+
985+
expect(isCalled).toEqual(true)
986+
})
876987
})
877988

878989
describe('reactive initial state', () => {

0 commit comments

Comments
 (0)