Skip to content

Commit

Permalink
fix(Form)!: include nested state in submit data (#3028)
Browse files Browse the repository at this point in the history
  • Loading branch information
romhml authored Jan 14, 2025
1 parent 865a47f commit de9ecb1
Show file tree
Hide file tree
Showing 4 changed files with 21 additions and 9 deletions.
2 changes: 1 addition & 1 deletion docs/content/3.components/form.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ This will give you access to the following:
| Name | Type |
| ---- | ---- |
| `submit()`{lang="ts-type"} | `Promise<void>`{lang="ts-type"} <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Triggers form submission.</p> |
| `validate(path?: string \| string[], opts: { silent?: boolean })`{lang="ts-type"} | `Promise<T>`{lang="ts-type"} <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Triggers form validation. Will raise any errors unless `opts.silent` is set to true.</p> |
| `validate(opts: { name?: string \| string[], silent?: boolean, nested?: boolean, transform?: boolean })`{lang="ts-type"} | `Promise<T>`{lang="ts-type"} <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Triggers form validation. Will raise any errors unless `opts.silent` is set to true.</p> |
| `clear(path?: string)`{lang="ts-type"} | `void` <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Clears form errors associated with a specific path. If no path is provided, clears all form errors.</p> |
| `getErrors(path?: string)`{lang="ts-type"} | `FormError[]`{lang="ts-type"} <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Retrieves form errors associated with a specific path. If no path is provided, returns all form errors.</p></div> |
| `setErrors(errors: FormError[], path?: string)`{lang="ts-type"} | `void` <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Sets form errors for a given path. If no path is provided, overrides all errors.</p> |
Expand Down
15 changes: 9 additions & 6 deletions src/runtime/components/Form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const parentBus = inject(
provide(formBusInjectionKey, bus)
const nestedForms = ref<Map<string | number, { validate: () => any }>>(new Map())
const nestedForms = ref<Map<string | number, { validate: typeof _validate }>>(new Map())
onMounted(async () => {
bus.on(async (event) => {
Expand Down Expand Up @@ -121,12 +121,12 @@ async function getErrors(): Promise<FormErrorWithId[]> {
return resolveErrorIds(errs)
}
async function _validate(opts: { name?: string | string[], silent?: boolean, nested?: boolean } = { silent: false, nested: true }): Promise<T | false> {
async function _validate(opts: { name?: string | string[], silent?: boolean, nested?: boolean, transform?: boolean } = { silent: false, nested: true, transform: false }): Promise<T | false> {
const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name as string[]
const nestedValidatePromises = !names && opts.nested
? Array.from(nestedForms.value.values()).map(
({ validate }) => validate().then(() => undefined).catch((error: Error) => {
({ validate }) => validate(opts).then(() => undefined).catch((error: Error) => {
if (!(error instanceof FormValidationException)) {
throw error
}
Expand All @@ -151,13 +151,17 @@ async function _validate(opts: { name?: string | string[], silent?: boolean, nes
errors.value = await getErrors()
}
const childErrors = (await Promise.all(nestedValidatePromises)).filter(val => val)
const childErrors = (await Promise.all(nestedValidatePromises)).filter(val => val !== undefined)
if (errors.value.length + childErrors.length > 0) {
if (opts.silent) return false
throw new FormValidationException(formId, errors.value, childErrors)
}
if (opts.transform) {
Object.assign(props.state, transformedState.value)
}
return props.state as T
}
Expand All @@ -170,8 +174,7 @@ async function onSubmitWrapper(payload: Event) {
const event = payload as FormSubmitEvent<any>
try {
await _validate({ nested: true })
event.data = props.schema ? transformedState.value : props.state
event.data = await _validate({ nested: true, transform: true })
await props.onSubmit?.(event)
} catch (error) {
if (!(error instanceof FormValidationException)) {
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/types/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { GetObjectField } from './utils'
import type { Struct as SuperstructSchema } from 'superstruct'

export interface Form<T> {
validate (opts?: { name: string | string[], silent?: false, nested?: boolean }): Promise<T | false>
validate (opts?: { name?: string | string[], silent?: boolean, nested?: boolean, transform?: boolean }): Promise<T | false>
clear (path?: string): void
errors: Ref<FormError[]>
setErrors (errs: FormError[], path?: string): void
Expand Down Expand Up @@ -95,7 +95,7 @@ export class FormValidationException extends Error {
errors: FormErrorWithId[]
children?: FormValidationException[]

constructor(formId: string | number, errors: FormErrorWithId[], childErrors: FormValidationException[]) {
constructor(formId: string | number, errors: FormErrorWithId[], childErrors?: FormValidationException[]) {
super('Form validation exception')
this.formId = formId
this.errors = errors
Expand Down
9 changes: 9 additions & 0 deletions test/components/Form.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,15 @@ describe('Form', () => {
expect(nestedField.text()).toBe('Required')
})

test('submit event contains nested attributes', async () => {
state.email = '[email protected]'
state.password = 'strongpassword'
state.nested.field = 'nested'

await form.value.submit()
expect(wrapper.setupState.onSubmit).toHaveBeenCalledWith(expect.objectContaining({ data: { email: '[email protected]', password: 'strongpassword', nested: { field: 'nested' } } }))
})

test('submit works when child is disabled', async () => {
await form.value.submit()
expect(wrapper.setupState.onError).toHaveBeenCalledTimes(1)
Expand Down

0 comments on commit de9ecb1

Please sign in to comment.