Skip to content

feature: on submit error #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "formango",
"type": "module",
"version": "2.0.28",
"version": "2.0.30",
"packageManager": "[email protected]",
"description": "",
"author": "Wouter Laermans <[email protected]>",
Expand Down
41 changes: 34 additions & 7 deletions src/lib/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,25 @@ interface UseFormReturnType<TSchema extends z.ZodType> {
* @param data The current form data.
*/
onSubmitForm: (cb: (data: z.infer<TSchema>) => void) => void
/**
* Called when the form is attempted to be submitted, but is invalid.
* Only called for client-side validation.
*/
onSubmitFormError: (cb: () => void) => void
/**
* The form instance itself.
*/
form: Form<TSchema>
}

interface UseFormOptions<TSchema extends z.ZodType> {
/**
* The zod schema of the form.
*/
schema: TSchema
/**
* The initial state of the form
*/
initialState?: MaybeRefOrGetter<NullableKeys<z.infer<TSchema>>>
}

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

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

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

// Check if the field is already registered
if (existingId !== null) {
const field = registeredFields.get(existingId) ?? null
let field = registeredFields.get(existingId) ?? null

if (field === null)
throw new Error(`${path} is already registered as a field array`)
if (field === null) {
const value = get(form, path)
field = createField(existingId, path, value)
}

// Check if value of the field is null or empty array, if so, set default value
const isEmpty = field.modelValue === null || (Array.isArray(field.modelValue) && field.modelValue.length === 0)

if (isEmpty && defaultValue !== undefined)
field.setValue(clonedDefaultValue)

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

if (fieldArray === null)
throw new Error(`${path} is already registered as a field`)
if (fieldArray === null) {
const value = get(form, path)
fieldArray = createFieldArray(existingId, path, value ?? [])
}

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

blurAll()

if (!isValid.value)
if (!isValid.value) {
onSubmitFormErrorCb?.()
return
}

// We need to keep track of the current form state, because the form might change while submitting
const currentFormState = deepClone(form)
Expand Down Expand Up @@ -642,5 +666,8 @@ export function useForm<TSchema extends z.ZodType>(
onSubmitForm: (cb: (data: z.infer<TSchema>) => MaybePromise<void>) => {
onSubmitCb = cb
},
onSubmitFormError: (cb: () => void) => {
onSubmitFormErrorCb = cb
},
}
}
111 changes: 111 additions & 0 deletions test/useForm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const basicArraySchema = z.object({
array: z.array(z.string()),
})

const basic2DArraySchema = z.object({
array: z.array(z.array(z.string())),
})

const objectArraySchema = z.object({
array: z.array(
z.object({
Expand Down Expand Up @@ -234,6 +238,92 @@ describe('useForm', () => {
],
})
})

it('should register a field as a fieldArray', () => {
const { form } = useForm({
schema: basicArraySchema,
})

const arrayAsField = form.register('array', ['John'])
const arrayAsArray = form.registerArray('array')

expect(form.state).toEqual({
array: ['John'],
})

expect(arrayAsField.modelValue).toEqual(['John'])
expect(arrayAsArray.modelValue).toEqual(['John'])

arrayAsField.setValue(['Doe'])

expect(form.state).toEqual({
array: ['Doe'],
})

expect(arrayAsField.modelValue).toEqual(['Doe'])
expect(arrayAsArray.modelValue).toEqual(['Doe'])

arrayAsArray.append('John')

expect(form.state).toEqual({
array: ['Doe', 'John'],
})

expect(arrayAsField.modelValue).toEqual(['Doe', 'John'])
expect(arrayAsArray.modelValue).toEqual(['Doe', 'John'])
})

it('should register a fieldArray as a field', () => {
const { form } = useForm({
schema: basicArraySchema,
})

const arrayAsArray = form.registerArray('array', ['John'])
const arrayAsField = form.register('array')

expect(form.state).toEqual({
array: ['John'],
})

expect(arrayAsField.modelValue).toEqual(['John'])
expect(arrayAsArray.modelValue).toEqual(['John'])

arrayAsField.setValue(['Doe'])

expect(form.state).toEqual({
array: ['Doe'],
})

expect(arrayAsField.modelValue).toEqual(['Doe'])
expect(arrayAsArray.modelValue).toEqual(['Doe'])

arrayAsArray.append('John')

expect(form.state).toEqual({
array: ['Doe', 'John'],
})

expect(arrayAsField.modelValue).toEqual(['Doe', 'John'])
expect(arrayAsArray.modelValue).toEqual(['Doe', 'John'])
})

it('should register a 2D array field with a default value', () => {
const { form } = useForm({
schema: basic2DArraySchema,
})

const array = form.registerArray('array', [[]])

expect(array.modelValue).toEqual([[]])

expect(form.state).toEqual({
array: [[]],
})

array.register('0', ['John'])

expect(array.modelValue).toEqual([['John']])
})
})

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

const array = form.registerArray('array')

array.register('0')

array.remove(1)

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

expect(submitted).toEqual(false)
})

it('should call `onSubmitError` if there are errors', async () => {
let isCalled = false

const { form, onSubmitFormError } = useForm({
schema: basicSchema,
})

onSubmitFormError(() => {
isCalled = true
})

form.register('name', 'Jon')

await sleep(0)
await form.submit()

expect(isCalled).toEqual(true)
})
})

describe('reactive initial state', () => {
Expand Down
Loading