Field validations can be expensive, especially custom validations that are async or highly complex. This can lead to slow form state response times when generating form state for many such fields. Ideally, we only run validations on fields whose values have changed. This is not possible, however, because field validation functions might reference _other_ field values with their args, and there is no good way of detecting exactly which fields should run in this case. The next best thing here is to only run validations _after the form has been submitted_, and then every `onChange` event thereafter until a successful submit has taken place. This is an elegant solution because we currently don't _render_ field errors until submission anyway. This change will significantly speed up form state response times, at least until the form has been submitted. From then on, all field validations will run regardless, just as they do now. If custom validations continue to slow down form state response times, there is a new `event` arg introduced in #10738 that can be used to control whether heavy operations occur on change or on submit. Related: #10638
125 lines
3.1 KiB
TypeScript
125 lines
3.1 KiB
TypeScript
'use client'
|
|
import type { FormProps, UserWithToken } from '@payloadcms/ui'
|
|
import type {
|
|
DocumentPreferences,
|
|
FormState,
|
|
LoginWithUsernameOptions,
|
|
SanitizedDocumentPermissions,
|
|
} from 'payload'
|
|
|
|
import {
|
|
ConfirmPasswordField,
|
|
EmailAndUsernameFields,
|
|
Form,
|
|
FormSubmit,
|
|
PasswordField,
|
|
RenderFields,
|
|
useAuth,
|
|
useConfig,
|
|
useServerFunctions,
|
|
useTranslation,
|
|
} from '@payloadcms/ui'
|
|
import { abortAndIgnore, handleAbortRef } from '@payloadcms/ui/shared'
|
|
import React, { useEffect } from 'react'
|
|
|
|
export const CreateFirstUserClient: React.FC<{
|
|
docPermissions: SanitizedDocumentPermissions
|
|
docPreferences: DocumentPreferences
|
|
initialState: FormState
|
|
loginWithUsername?: false | LoginWithUsernameOptions
|
|
userSlug: string
|
|
}> = ({ docPermissions, docPreferences, initialState, loginWithUsername, userSlug }) => {
|
|
const {
|
|
config: {
|
|
routes: { admin, api: apiRoute },
|
|
serverURL,
|
|
},
|
|
getEntityConfig,
|
|
} = useConfig()
|
|
|
|
const { getFormState } = useServerFunctions()
|
|
|
|
const { t } = useTranslation()
|
|
const { setUser } = useAuth()
|
|
|
|
const abortOnChangeRef = React.useRef<AbortController>(null)
|
|
|
|
const collectionConfig = getEntityConfig({ collectionSlug: userSlug })
|
|
|
|
const onChange: FormProps['onChange'][0] = React.useCallback(
|
|
async ({ formState: prevFormState, submitted }) => {
|
|
const controller = handleAbortRef(abortOnChangeRef)
|
|
|
|
const response = await getFormState({
|
|
collectionSlug: userSlug,
|
|
docPermissions,
|
|
docPreferences,
|
|
formState: prevFormState,
|
|
operation: 'create',
|
|
schemaPath: userSlug,
|
|
signal: controller.signal,
|
|
skipValidation: !submitted,
|
|
})
|
|
|
|
abortOnChangeRef.current = null
|
|
|
|
if (response && response.state) {
|
|
return response.state
|
|
}
|
|
},
|
|
[userSlug, getFormState, docPermissions, docPreferences],
|
|
)
|
|
|
|
const handleFirstRegister = (data: UserWithToken) => {
|
|
setUser(data)
|
|
}
|
|
|
|
useEffect(() => {
|
|
const abortOnChange = abortOnChangeRef.current
|
|
|
|
return () => {
|
|
abortAndIgnore(abortOnChange)
|
|
}
|
|
}, [])
|
|
|
|
return (
|
|
<Form
|
|
action={`${serverURL}${apiRoute}/${userSlug}/first-register`}
|
|
initialState={initialState}
|
|
method="POST"
|
|
onChange={[onChange]}
|
|
onSuccess={handleFirstRegister}
|
|
redirect={admin}
|
|
validationOperation="create"
|
|
>
|
|
<EmailAndUsernameFields
|
|
className="emailAndUsername"
|
|
loginWithUsername={loginWithUsername}
|
|
operation="create"
|
|
readOnly={false}
|
|
t={t}
|
|
/>
|
|
<PasswordField
|
|
autoComplete="off"
|
|
field={{
|
|
name: 'password',
|
|
label: t('authentication:newPassword'),
|
|
required: true,
|
|
}}
|
|
path="password"
|
|
/>
|
|
<ConfirmPasswordField />
|
|
<RenderFields
|
|
fields={collectionConfig.fields}
|
|
forceRender
|
|
parentIndexPath=""
|
|
parentPath=""
|
|
parentSchemaPath={userSlug}
|
|
permissions={true}
|
|
readOnly={false}
|
|
/>
|
|
<FormSubmit size="large">{t('general:create')}</FormSubmit>
|
|
</Form>
|
|
)
|
|
}
|