diff --git a/.changeset/olive-pears-sell.md b/.changeset/olive-pears-sell.md new file mode 100644 index 00000000000..369f64b04dc --- /dev/null +++ b/.changeset/olive-pears-sell.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/ui-client': patch +--- + +Fixed required custom fields considering blank spaces as valid. diff --git a/packages/ui-client/src/components/CustomFieldsForm.tsx b/packages/ui-client/src/components/CustomFieldsForm.tsx index dee63d50106..49d4a120e47 100644 --- a/packages/ui-client/src/components/CustomFieldsForm.tsx +++ b/packages/ui-client/src/components/CustomFieldsForm.tsx @@ -3,8 +3,9 @@ import type { SelectOption } from '@rocket.chat/fuselage'; import { Field, Select, TextInput } from '@rocket.chat/fuselage'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { useTranslation } from '@rocket.chat/ui-contexts'; +import { useCallback, useMemo } from 'react'; import type { Control, FieldValues } from 'react-hook-form'; -import { Controller } from 'react-hook-form'; +import { Controller, useFormState, get } from 'react-hook-form'; type CustomFieldFormProps = { metadata: CustomFieldMetadata[]; @@ -33,32 +34,41 @@ const CustomField = ({ ...props }: CustomFieldProps) => { const t = useTranslation(); - const { getFieldState } = control; + const { errors } = useFormState({ control }); const Component = FIELD_TYPES[type] ?? null; - const selectOptions = - options.length > 0 && options[0] instanceof Array ? options : options.map((option) => [option, option, defaultValue === option]); + const selectOptions = useMemo( + () => + options.length > 0 && options[0] instanceof Array ? options : options.map((option) => [option, option, defaultValue === option]), + [defaultValue, options], + ); + + const validateRequired = useCallback((value) => (required ? typeof value === 'string' && !!value.trim() : true), [required]); - const getErrorMessage = (error: any) => { - switch (error?.type) { - case 'required': - return t('The_field_is_required', label || name); - case 'minLength': - return t('Min_length_is', props?.minLength); - case 'maxLength': - return t('Max_length_is', props?.maxLength); - } - }; + const getErrorMessage = useCallback( + (error: any) => { + switch (error?.type) { + case 'required': + return t('The_field_is_required', label || name); + case 'minLength': + return t('Min_length_is', props?.minLength); + case 'maxLength': + return t('Max_length_is', props?.maxLength); + } + }, + [label, name, props?.maxLength, props?.minLength, t], + ); - const error = getErrorMessage(getFieldState(name as any).error); + const error = get(errors, name); + const errorMessage = useMemo(() => getErrorMessage(error), [error, getErrorMessage]); return ( name={name} control={control} defaultValue={defaultValue ?? ''} - rules={{ required, minLength: props.minLength, maxLength: props.maxLength }} + rules={{ minLength: props.minLength, maxLength: props.maxLength, validate: { required: validateRequired } }} render={({ field }) => ( @@ -66,9 +76,9 @@ const CustomField = ({ {required && '*'} - + - {error} + {errorMessage} )} />