Field validations currently run very often, such as within form state on
type. This can lead to serious performance implications within the admin
panel if those validation functions are async, especially if they
perform expensive database queries. One glaring example of this is how
all relationship and upload fields perform a database lookup in order to
evaluate that the given value(s) satisfy the defined filter options. If
the field is polymorphic, this can happen multiple times over, one for
each collection. Similarly, custom validation functions might also
perform expensive tasks, something that Payload has no control over.
The fix here is two-fold. First, we now provide a new `event` arg to all
`validate` functions that allow you to opt-in to performing expensive
operations _only when documents are submitted_, and fallback to
significantly more performant validations as form state is generated.
This new pattern will be the new default for relationship and upload
fields, however, any custom validation functions will need to be
implemented in this way in order to take advantage of it. Here's what
that might look like:
```
[
// ...
{
name: 'text'
type: 'text',
validate: async (value, { event }) => {
if (event === 'onChange') {
// Do something highly performant here
return true
}
// Do something more expensive here
return true
}
}
]
```
The second part of this is to only run validations _after the form as
been submitted_, and then every change event thereafter. This work is
being done in #10580.
119 lines
3.0 KiB
TypeScript
119 lines
3.0 KiB
TypeScript
'use client'
|
|
import type { PasswordFieldValidation, PayloadRequest } from 'payload'
|
|
|
|
import { password } from 'payload/shared'
|
|
import React, { useCallback, useMemo } from 'react'
|
|
|
|
import type { PasswordFieldProps } from './types.js'
|
|
|
|
import { useField } from '../../forms/useField/index.js'
|
|
import { withCondition } from '../../forms/withCondition/index.js'
|
|
import { useConfig } from '../../providers/Config/index.js'
|
|
import { useLocale } from '../../providers/Locale/index.js'
|
|
import { useTranslation } from '../../providers/Translation/index.js'
|
|
import { mergeFieldStyles } from '../mergeFieldStyles.js'
|
|
import './index.scss'
|
|
import { isFieldRTL } from '../shared/index.js'
|
|
import { PasswordInput } from './input.js'
|
|
|
|
const PasswordFieldComponent: React.FC<PasswordFieldProps> = (props) => {
|
|
const {
|
|
autoComplete,
|
|
field,
|
|
field: {
|
|
admin: {
|
|
className,
|
|
disabled: disabledFromProps,
|
|
placeholder,
|
|
rtl,
|
|
} = {} as PasswordFieldProps['field']['admin'],
|
|
label,
|
|
localized,
|
|
required,
|
|
} = {} as PasswordFieldProps['field'],
|
|
inputRef,
|
|
path,
|
|
validate,
|
|
} = props
|
|
|
|
const { t } = useTranslation()
|
|
const locale = useLocale()
|
|
const { config } = useConfig()
|
|
|
|
const memoizedValidate: PasswordFieldValidation = useCallback(
|
|
(value, options) => {
|
|
if (typeof validate === 'function') {
|
|
return validate(value, { ...options, required })
|
|
}
|
|
|
|
return password(value, {
|
|
name: 'password',
|
|
type: 'text',
|
|
data: {},
|
|
event: 'onChange',
|
|
preferences: { fields: {} },
|
|
req: {
|
|
payload: {
|
|
config,
|
|
},
|
|
t,
|
|
} as unknown as PayloadRequest,
|
|
required: true,
|
|
siblingData: {},
|
|
})
|
|
},
|
|
[validate, config, t, required],
|
|
)
|
|
|
|
const {
|
|
customComponents: { AfterInput, BeforeInput, Description, Error, Label } = {},
|
|
formInitializing,
|
|
formProcessing,
|
|
setValue,
|
|
showError,
|
|
value,
|
|
} = useField({
|
|
path,
|
|
validate: memoizedValidate,
|
|
})
|
|
|
|
const disabled = disabledFromProps || formInitializing || formProcessing
|
|
|
|
const renderRTL = isFieldRTL({
|
|
fieldLocalized: false,
|
|
fieldRTL: rtl,
|
|
locale,
|
|
localizationConfig: config.localization || undefined,
|
|
})
|
|
|
|
const styles = useMemo(() => mergeFieldStyles(field), [field])
|
|
|
|
return (
|
|
<PasswordInput
|
|
AfterInput={AfterInput}
|
|
autoComplete={autoComplete}
|
|
BeforeInput={BeforeInput}
|
|
className={className}
|
|
Description={Description}
|
|
Error={Error}
|
|
inputRef={inputRef}
|
|
Label={Label}
|
|
label={label}
|
|
localized={localized}
|
|
onChange={(e) => {
|
|
setValue(e.target.value)
|
|
}}
|
|
path={path}
|
|
placeholder={placeholder}
|
|
readOnly={disabled}
|
|
required={required}
|
|
rtl={renderRTL}
|
|
showError={showError}
|
|
style={styles}
|
|
value={(value as string) || ''}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export const PasswordField = withCondition(PasswordFieldComponent)
|