diff --git a/examples/multi-tenant/src/app/(payload)/admin/importMap.js b/examples/multi-tenant/src/app/(payload)/admin/importMap.js index 5273adf95d6..694b5399adf 100644 --- a/examples/multi-tenant/src/app/(payload)/admin/importMap.js +++ b/examples/multi-tenant/src/app/(payload)/admin/importMap.js @@ -3,7 +3,10 @@ import { TenantSelector as TenantSelector_d6d5f193a167989e2ee7d14202901e62 } fro import { TenantSelectionProvider as TenantSelectionProvider_1d0591e3cf4f332c83a86da13a0de59a } from '@payloadcms/plugin-multi-tenant/client' export const importMap = { - "@payloadcms/plugin-multi-tenant/client#TenantField": TenantField_1d0591e3cf4f332c83a86da13a0de59a, - "@payloadcms/plugin-multi-tenant/rsc#TenantSelector": TenantSelector_d6d5f193a167989e2ee7d14202901e62, - "@payloadcms/plugin-multi-tenant/client#TenantSelectionProvider": TenantSelectionProvider_1d0591e3cf4f332c83a86da13a0de59a + '@payloadcms/plugin-multi-tenant/client#TenantField': + TenantField_1d0591e3cf4f332c83a86da13a0de59a, + '@payloadcms/plugin-multi-tenant/rsc#TenantSelector': + TenantSelector_d6d5f193a167989e2ee7d14202901e62, + '@payloadcms/plugin-multi-tenant/client#TenantSelectionProvider': + TenantSelectionProvider_1d0591e3cf4f332c83a86da13a0de59a, } diff --git a/packages/next/src/views/Account/index.tsx b/packages/next/src/views/Account/index.tsx index 83efb6aa326..eccabbb36e6 100644 --- a/packages/next/src/views/Account/index.tsx +++ b/packages/next/src/views/Account/index.tsx @@ -88,6 +88,7 @@ export const Account: React.FC = async ({ renderAllFields: true, req, schemaPath: collectionConfig.slug, + skipValidation: true, }) // Fetch document lock state diff --git a/packages/next/src/views/CreateFirstUser/index.client.tsx b/packages/next/src/views/CreateFirstUser/index.client.tsx index 938d2d6bf20..caaabdaa092 100644 --- a/packages/next/src/views/CreateFirstUser/index.client.tsx +++ b/packages/next/src/views/CreateFirstUser/index.client.tsx @@ -47,7 +47,7 @@ export const CreateFirstUserClient: React.FC<{ const collectionConfig = getEntityConfig({ collectionSlug: userSlug }) const onChange: FormProps['onChange'][0] = React.useCallback( - async ({ formState: prevFormState }) => { + async ({ formState: prevFormState, submitted }) => { const controller = handleAbortRef(abortOnChangeRef) const response = await getFormState({ @@ -58,6 +58,7 @@ export const CreateFirstUserClient: React.FC<{ operation: 'create', schemaPath: userSlug, signal: controller.signal, + skipValidation: !submitted, }) abortOnChangeRef.current = null diff --git a/packages/next/src/views/CreateFirstUser/index.tsx b/packages/next/src/views/CreateFirstUser/index.tsx index e603ec93dda..3840a8ea486 100644 --- a/packages/next/src/views/CreateFirstUser/index.tsx +++ b/packages/next/src/views/CreateFirstUser/index.tsx @@ -63,6 +63,7 @@ export const CreateFirstUserView: React.FC = async ({ initPageRe renderAllFields: true, req, schemaPath: collectionConfig.slug, + skipValidation: true, }) return ( diff --git a/packages/next/src/views/Document/index.tsx b/packages/next/src/views/Document/index.tsx index eb7376b4b4d..e4dc94b6a0e 100644 --- a/packages/next/src/views/Document/index.tsx +++ b/packages/next/src/views/Document/index.tsx @@ -157,6 +157,7 @@ export const renderDocument = async ({ renderAllFields: true, req, schemaPath: collectionSlug || globalSlug, + skipValidation: true, }), ]) diff --git a/packages/next/src/views/LivePreview/index.client.tsx b/packages/next/src/views/LivePreview/index.client.tsx index 5a2e81be59f..33c3c9997dd 100644 --- a/packages/next/src/views/LivePreview/index.client.tsx +++ b/packages/next/src/views/LivePreview/index.client.tsx @@ -225,6 +225,7 @@ const PreviewView: React.FC = ({ returnLockStatus: false, schemaPath: entitySlug, signal: controller.signal, + skipValidation: true, }) // Unlock the document after save @@ -267,7 +268,7 @@ const PreviewView: React.FC = ({ ) const onChange: FormProps['onChange'][0] = useCallback( - async ({ formState: prevFormState }) => { + async ({ formState: prevFormState, submitted }) => { const controller = handleAbortRef(abortOnChangeRef) const currentTime = Date.now() @@ -292,6 +293,7 @@ const PreviewView: React.FC = ({ returnLockStatus: isLockingEnabled ? true : false, schemaPath, signal: controller.signal, + skipValidation: !submitted, updateLastEdited, }) diff --git a/packages/payload/src/admin/forms/Form.ts b/packages/payload/src/admin/forms/Form.ts index 28dd8422d4a..263e97214e2 100644 --- a/packages/payload/src/admin/forms/Form.ts +++ b/packages/payload/src/admin/forms/Form.ts @@ -84,6 +84,7 @@ export type BuildFormStateArgs = { req: PayloadRequest returnLockStatus?: boolean schemaPath: string + skipValidation?: boolean updateLastEdited?: boolean } & ( | { diff --git a/packages/ui/src/elements/BulkUpload/EditForm/index.tsx b/packages/ui/src/elements/BulkUpload/EditForm/index.tsx index fc894551fc5..1a95d3ae6b7 100644 --- a/packages/ui/src/elements/BulkUpload/EditForm/index.tsx +++ b/packages/ui/src/elements/BulkUpload/EditForm/index.tsx @@ -108,7 +108,7 @@ export function EditForm({ submitted }: EditFormProps) { ) const onChange: NonNullable[0] = useCallback( - async ({ formState: prevFormState }) => { + async ({ formState: prevFormState, submitted }) => { const controller = handleAbortRef(abortOnChangeRef) const docPreferences = await getDocPreferences() @@ -121,6 +121,7 @@ export function EditForm({ submitted }: EditFormProps) { operation: 'create', schemaPath, signal: controller.signal, + skipValidation: !submitted, }) abortOnChangeRef.current = null diff --git a/packages/ui/src/elements/BulkUpload/FormsManager/index.tsx b/packages/ui/src/elements/BulkUpload/FormsManager/index.tsx index 517f081b5ff..762b49493e0 100644 --- a/packages/ui/src/elements/BulkUpload/FormsManager/index.tsx +++ b/packages/ui/src/elements/BulkUpload/FormsManager/index.tsx @@ -216,6 +216,7 @@ export function FormsManagerProvider({ children }: FormsManagerProps) { operation: 'create', renderAllFields: true, schemaPath: collectionSlug, + skipValidation: true, }) initialStateRef.current = formStateWithoutFiles setHasInitializedState(true) diff --git a/packages/ui/src/elements/EditMany/DrawerContent.tsx b/packages/ui/src/elements/EditMany/DrawerContent.tsx index e802b1b1d4c..98f09d7facc 100644 --- a/packages/ui/src/elements/EditMany/DrawerContent.tsx +++ b/packages/ui/src/elements/EditMany/DrawerContent.tsx @@ -185,6 +185,7 @@ export const EditManyDrawerContent: React.FC< operation: 'update', schemaPath: slug, signal: controller.signal, + skipValidation: true, }) setInitialState(result) diff --git a/packages/ui/src/forms/Form/index.tsx b/packages/ui/src/forms/Form/index.tsx index 663b6e91061..091334779a6 100644 --- a/packages/ui/src/forms/Form/index.tsx +++ b/packages/ui/src/forms/Form/index.tsx @@ -505,6 +505,7 @@ export const Form: React.FC = (props) => { renderAllFields: true, schemaPath: collectionSlug ? collectionSlug : globalSlug, signal: controller.signal, + skipValidation: true, }) contextRef.current = { ...initContextState } as FormContextType @@ -665,6 +666,7 @@ export const Form: React.FC = (props) => { // Edit view default onChange is in packages/ui/src/views/Edit/index.tsx. This onChange usually sends a form state request revalidatedFormState = await onChangeFn({ formState: deepCopyObjectSimpleWithoutReactComponents(contextRef.current.fields), + submitted, }) } @@ -699,7 +701,7 @@ export const Form: React.FC = (props) => { `fields` updates before `modified`, because setModified is in a setTimeout. So on the first change, modified is false, so we don't trigger the effect even though we should. **/ - [contextRef.current.fields, modified], + [contextRef.current.fields, modified, submitted], [dispatchFields, onChange], { delay: 250, diff --git a/packages/ui/src/forms/Form/types.ts b/packages/ui/src/forms/Form/types.ts index 896dbdcd6a6..cb586d7528d 100644 --- a/packages/ui/src/forms/Form/types.ts +++ b/packages/ui/src/forms/Form/types.ts @@ -39,7 +39,7 @@ export type FormProps = { initialState?: FormState isInitializing?: boolean log?: boolean - onChange?: ((args: { formState: FormState }) => Promise)[] + onChange?: ((args: { formState: FormState; submitted?: boolean }) => Promise)[] onSubmit?: (fields: FormState, data: Data) => void onSuccess?: (json: unknown) => Promise | void redirect?: string diff --git a/packages/ui/src/forms/fieldSchemasToFormState/index.tsx b/packages/ui/src/forms/fieldSchemasToFormState/index.tsx index 38f06f27245..4eabe26cc82 100644 --- a/packages/ui/src/forms/fieldSchemasToFormState/index.tsx +++ b/packages/ui/src/forms/fieldSchemasToFormState/index.tsx @@ -47,34 +47,33 @@ type Args = { renderAllFields: boolean renderFieldFn?: RenderFieldMethod req: PayloadRequest - schemaPath: string + skipValidation?: boolean } -export const fieldSchemasToFormState = async (args: Args): Promise => { - if (!args.clientFieldSchemaMap && args.renderFieldFn) { +export const fieldSchemasToFormState = async ({ + id, + clientFieldSchemaMap, + collectionSlug, + data = {}, + fields, + fieldSchemaMap, + operation, + permissions, + preferences, + previousFormState, + renderAllFields, + renderFieldFn, + req, + schemaPath, + skipValidation, +}: Args): Promise => { + if (!clientFieldSchemaMap && renderFieldFn) { console.warn( 'clientFieldSchemaMap is not passed to fieldSchemasToFormState - this will reduce performance', ) } - const { - id, - clientFieldSchemaMap, - collectionSlug, - data = {}, - fields, - fieldSchemaMap, - operation, - permissions, - preferences, - previousFormState, - renderAllFields, - renderFieldFn, - req, - schemaPath, - } = args - if (fields && fields.length) { const state: FormStateWithoutComponents = {} @@ -110,6 +109,7 @@ export const fieldSchemasToFormState = async (args: Args): Promise => renderAllFields, renderFieldFn, req, + skipValidation, state, }) diff --git a/packages/ui/src/utilities/buildFormState.ts b/packages/ui/src/utilities/buildFormState.ts index 57d086be31c..0b573b03d14 100644 --- a/packages/ui/src/utilities/buildFormState.ts +++ b/packages/ui/src/utilities/buildFormState.ts @@ -114,6 +114,7 @@ export const buildFormState = async ( }, returnLockStatus, schemaPath = collectionSlug || globalSlug, + skipValidation, updateLastEdited, } = args @@ -194,6 +195,7 @@ export const buildFormState = async ( renderFieldFn: renderField, req, schemaPath, + skipValidation, }) // Maintain form state of auth / upload fields diff --git a/packages/ui/src/views/Edit/index.tsx b/packages/ui/src/views/Edit/index.tsx index b1071223153..7065b98daaf 100644 --- a/packages/ui/src/views/Edit/index.tsx +++ b/packages/ui/src/views/Edit/index.tsx @@ -280,6 +280,7 @@ export const DefaultEditView: React.FC = ({ returnLockStatus: false, schemaPath: schemaPathSegments.join('.'), signal: controller.signal, + skipValidation: true, }) // Unlock the document after save @@ -323,7 +324,7 @@ export const DefaultEditView: React.FC = ({ ) const onChange: FormProps['onChange'][0] = useCallback( - async ({ formState: prevFormState }) => { + async ({ formState: prevFormState, submitted }) => { const controller = handleAbortRef(abortOnChangeRef) const currentTime = Date.now() @@ -345,6 +346,7 @@ export const DefaultEditView: React.FC = ({ formState: prevFormState, globalSlug, operation, + skipValidation: !submitted, // Performance optimization: Setting it to false ensure that only fields that have explicit requireRender set in the form state will be rendered (e.g. new array rows). // We only want to render ALL fields on initial render, not in onChange. renderAllFields: false,