From aecdb833c2e6f2c573bfc8c031e2934dadf5245b Mon Sep 17 00:00:00 2001 From: Vedanta Somnathe Date: Sun, 20 Apr 2025 13:58:25 -0500 Subject: [PATCH 1/3] preliminary approach for merging server errors --- .../next-server-actions/src/app/action.ts | 5 +-- .../src/app/client-component.tsx | 7 ---- .../src/app/shared-code.ts | 1 - packages/form-core/src/FormApi.ts | 40 +++++++++++++++++++ .../src/nextjs/createServerValidate.ts | 15 ++++++- 5 files changed, 56 insertions(+), 12 deletions(-) diff --git a/examples/react/next-server-actions/src/app/action.ts b/examples/react/next-server-actions/src/app/action.ts index 8ffa3fc02..2efca028b 100644 --- a/examples/react/next-server-actions/src/app/action.ts +++ b/examples/react/next-server-actions/src/app/action.ts @@ -9,9 +9,8 @@ import { formOpts } from './shared-code' const serverValidate = createServerValidate({ ...formOpts, onServerValidate: ({ value }) => { - if (value.age < 12) { - return 'Server validation: You must be at least 12 to sign up' - } + if (value.age < 12) + return { age: 'Server validation: You must be at least 12 to sign up' } }, }) diff --git a/examples/react/next-server-actions/src/app/client-component.tsx b/examples/react/next-server-actions/src/app/client-component.tsx index b7e62909c..a7104e555 100644 --- a/examples/react/next-server-actions/src/app/client-component.tsx +++ b/examples/react/next-server-actions/src/app/client-component.tsx @@ -3,7 +3,6 @@ import { useActionState } from 'react' import { mergeForm, useForm, useTransform } from '@tanstack/react-form' import { initialFormState } from '@tanstack/react-form/nextjs' -import { useStore } from '@tanstack/react-store' import someAction from './action' import { formOpts } from './shared-code' @@ -18,14 +17,8 @@ export const ClientComp = () => { ), }) - const formErrors = useStore(form.store, (formState) => formState.errors) - return (
form.handleSubmit()}> - {formErrors.map((error) => ( -

{error}

- ))} - { + void (Object.values(this.fieldInfo) as FieldInfo[]).forEach( + (field) => { + const fieldInstance = field.instance + + if (!fieldInstance) return + + if (fieldInstance.name in serverErrorMap) { + fieldInstance.setMeta((prev) => ({ + ...prev, + errorMap: { + ...prev.errorMap, + onServer: serverErrorMap[fieldInstance.name], + }, + })) + fieldInstance.mount() + } + + this.validateField(fieldInstance.name, 'server') + }, + ) + }) } return state @@ -1388,6 +1413,20 @@ export class FormApi< }, })) } + + /** + * when we have an error for onServer in the state, we want + * to clear the error as soon as the user changes the value in the field + */ + if (cause !== 'server') { + this.baseStore.setState((prev) => ({ + ...prev, + errorMap: { + ...prev.errorMap, + onServer: undefined, + }, + })) + } }) return { hasErrored, fieldsErrorMap: currentValidationErrorMap } @@ -1774,6 +1813,7 @@ export class FormApi< // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition ...prev?.errorMap, onMount: undefined, + onServer: undefined, }, })) } diff --git a/packages/react-form/src/nextjs/createServerValidate.ts b/packages/react-form/src/nextjs/createServerValidate.ts index adaa4de1f..2b2934494 100644 --- a/packages/react-form/src/nextjs/createServerValidate.ts +++ b/packages/react-form/src/nextjs/createServerValidate.ts @@ -104,12 +104,25 @@ export const createServerValidate = : onServerError ) as UnwrapFormAsyncValidateOrFn + // Extract string values from errors if they're in object format + const errorsArray = onServerErrorVal + ? Array.isArray(onServerErrorVal) + ? onServerErrorVal.map((err) => + typeof err === 'object' ? Object.values(err)[0] : err, + ) + : [ + typeof onServerErrorVal === 'object' + ? Object.values(onServerErrorVal)[0] + : onServerErrorVal, + ] + : [] + const formState: ServerFormState = { errorMap: { onServer: onServerError, }, values, - errors: onServerErrorVal ? [onServerErrorVal] : [], + errors: errorsArray, } throw new ServerValidateError({ From a9d48c9c570c4200991425af92fa46b7f282ed97 Mon Sep 17 00:00:00 2001 From: Vedanta Somnathe Date: Wed, 7 May 2025 12:42:22 +0530 Subject: [PATCH 2/3] server map checked for empty value --- packages/form-core/src/FormApi.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/form-core/src/FormApi.ts b/packages/form-core/src/FormApi.ts index 0ca35301a..06cf8c527 100644 --- a/packages/form-core/src/FormApi.ts +++ b/packages/form-core/src/FormApi.ts @@ -1022,7 +1022,11 @@ export class FormApi< state = newObj.state this.prevTransformArray = transformArray - const serverErrorMap = this.store.state.errorMap['onServer'] as any + const serverErrorMap = this.store.state.errorMap['onServer'] as + | Record + | undefined + + if (!serverErrorMap) return state batch(() => { void (Object.values(this.fieldInfo) as FieldInfo[]).forEach( From 9413ca6961e52e382272945336be99f5ece5295a Mon Sep 17 00:00:00 2001 From: Vedanta Somnathe Date: Mon, 30 Jun 2025 17:07:51 +0530 Subject: [PATCH 3/3] using the global error type --- .../next-server-actions/src/app/action.ts | 7 ++++++- .../src/nextjs/createServerValidate.ts | 20 +++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/examples/react/next-server-actions/src/app/action.ts b/examples/react/next-server-actions/src/app/action.ts index 2efca028b..03154ec75 100644 --- a/examples/react/next-server-actions/src/app/action.ts +++ b/examples/react/next-server-actions/src/app/action.ts @@ -10,7 +10,12 @@ const serverValidate = createServerValidate({ ...formOpts, onServerValidate: ({ value }) => { if (value.age < 12) - return { age: 'Server validation: You must be at least 12 to sign up' } + return { + fields: { + age: 'Server validation: You must be at least 12 to sign up', + }, + form: 'Form level Error: Age must be at least 12', + } }, }) diff --git a/packages/react-form/src/nextjs/createServerValidate.ts b/packages/react-form/src/nextjs/createServerValidate.ts index 2b2934494..fc1bdaaae 100644 --- a/packages/react-form/src/nextjs/createServerValidate.ts +++ b/packages/react-form/src/nextjs/createServerValidate.ts @@ -96,13 +96,21 @@ export const createServerValidate = validationSource: 'form', })) as UnwrapFormAsyncValidateOrFn | undefined + console.log('ON SERVER ERROR', onServerError) + if (!onServerError) return values - const onServerErrorVal = ( - isGlobalFormValidationError(onServerError) - ? onServerError.form - : onServerError - ) as UnwrapFormAsyncValidateOrFn + let onServerErrorVal = undefined + let onServerErrorValFields = undefined + + if (isGlobalFormValidationError(onServerError)) { + onServerErrorVal = + onServerError.form as UnwrapFormAsyncValidateOrFn + onServerErrorValFields = + onServerError.fields as UnwrapFormAsyncValidateOrFn + } else { + onServerErrorVal = onServerError as UnwrapFormAsyncValidateOrFn + } // Extract string values from errors if they're in object format const errorsArray = onServerErrorVal @@ -119,7 +127,7 @@ export const createServerValidate = const formState: ServerFormState = { errorMap: { - onServer: onServerError, + onServer: onServerErrorValFields, }, values, errors: errorsArray,