From a0275943078eb526f84500ba7d5d1736d76cbf64 Mon Sep 17 00:00:00 2001 From: Komoszek Date: Thu, 20 Nov 2025 21:22:34 +0100 Subject: [PATCH 1/3] fix: include ReactElement in errors/warnings type --- docs/examples/validate-perf.tsx | 4 ++-- src/Field.tsx | 17 +++++++++-------- src/interface.ts | 28 +++++++++++++++------------- src/useForm.ts | 11 ++++++----- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/docs/examples/validate-perf.tsx b/docs/examples/validate-perf.tsx index 025694d69..c13c41126 100644 --- a/docs/examples/validate-perf.tsx +++ b/docs/examples/validate-perf.tsx @@ -4,7 +4,7 @@ import React from 'react'; import Form, { Field, FormInstance } from 'rc-field-form'; import Input from './components/Input'; import LabelField from './components/LabelField'; -import { ValidateMessages } from '@/interface'; +import { Message, ValidateMessages } from '@/interface'; const myMessages: ValidateMessages = { default: '${name} 看起来怪怪的……', @@ -34,7 +34,7 @@ export default class Demo extends React.Component { console.log('Failed:', errorInfo); }; - public onPasswordError = ({ errors }: { errors: string[] }) => { + public onPasswordError = ({ errors }: { errors: Message[] }) => { console.log('🐞 Password Error:', errors); }; diff --git a/src/Field.tsx b/src/Field.tsx index 69e680ce8..e1ac55e98 100644 --- a/src/Field.tsx +++ b/src/Field.tsx @@ -10,6 +10,7 @@ import type { InternalFormInstance, InternalNamePath, InternalValidateOptions, + Message, Meta, NamePath, NotifyInfo, @@ -29,8 +30,8 @@ import { getValue, } from './utils/valueUtil'; -const EMPTY_ERRORS: any[] = []; -const EMPTY_WARNINGS: any[] = []; +const EMPTY_ERRORS: never[] = []; +const EMPTY_WARNINGS: never[] = []; export type ShouldUpdate = | boolean @@ -137,12 +138,12 @@ class Field extends React.Component implements F */ private dirty: boolean = false; - private validatePromise: Promise | null; + private validatePromise: Promise | null; private prevValidating: boolean; - private errors: string[] = EMPTY_ERRORS; - private warnings: string[] = EMPTY_WARNINGS; + private errors: Message[] = EMPTY_ERRORS; + private warnings: Message[] = EMPTY_WARNINGS; // ============================== Subscriptions ============================== constructor(props: InternalFieldProps) { @@ -392,7 +393,7 @@ class Field extends React.Component implements F const { triggerName, validateOnly = false } = options || {}; // Force change to async to avoid rule OOD under renderProps field - const rootPromise = Promise.resolve().then(async (): Promise => { + const rootPromise = Promise.resolve().then(async (): Promise => { if (!this.mounted) { return []; } @@ -442,8 +443,8 @@ class Field extends React.Component implements F this.validatePromise = null; // Get errors & warnings - const nextErrors: string[] = []; - const nextWarnings: string[] = []; + const nextErrors: Message[] = []; + const nextWarnings: Message[] = []; ruleErrors.forEach?.(({ rule: { warningOnly }, errors = EMPTY_ERRORS }) => { if (warningOnly) { nextWarnings.push(...errors); diff --git a/src/interface.ts b/src/interface.ts index b8f30b730..ec2cb9393 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -9,11 +9,13 @@ export type NamePath = DeepNamePath; export type StoreValue = any; export type Store = Record; +export type Message = string | ReactElement; + export interface Meta { touched: boolean; validating: boolean; - errors: string[]; - warnings: string[]; + errors: Message[]; + warnings: Message[]; name: InternalNamePath; validated: boolean; } @@ -54,7 +56,7 @@ export type RuleRender = (form: FormInstance) => RuleObject; export interface ValidatorRule { warningOnly?: boolean; - message?: string | ReactElement; + message?: Message; validator: Validator; } @@ -63,7 +65,7 @@ interface BaseRule { enum?: StoreValue[]; len?: number; max?: number; - message?: string | ReactElement; + message?: Message; min?: number; pattern?: RegExp; required?: boolean; @@ -87,9 +89,9 @@ export type RuleObject = AggregationRule | ArrayRule; export type Rule = RuleObject | RuleRender; export interface ValidateErrorEntity { - message: string; + message: Message; values: Values; - errorFields: { name: InternalNamePath; errors: string[] }[]; + errorFields: { name: InternalNamePath; errors: Message[] }[]; outOfDate: boolean; } @@ -108,8 +110,8 @@ export interface FieldEntity { validateRules: (options?: InternalValidateOptions) => Promise; getMeta: () => Meta; getNamePath: () => InternalNamePath; - getErrors: () => string[]; - getWarnings: () => string[]; + getErrors: () => Message[]; + getWarnings: () => Message[]; props: { name?: NamePath; rules?: Rule[]; @@ -126,12 +128,12 @@ export interface FieldEntity { export interface FieldError { name: InternalNamePath; - errors: string[]; - warnings: string[]; + errors: Message[]; + warnings: Message[]; } export interface RuleError { - errors: string[]; + errors: Message[]; rule: RuleObject; } @@ -271,9 +273,9 @@ export interface FormInstance { getFieldsValue: (() => Values) & ((nameList: NamePath[] | true, filterFunc?: FilterFunc) => any) & ((config: GetFieldsValueConfig) => any); - getFieldError: (name: NamePath) => string[]; + getFieldError: (name: NamePath) => Message[]; getFieldsError: (nameList?: NamePath[]) => FieldError[]; - getFieldWarning: (name: NamePath) => string[]; + getFieldWarning: (name: NamePath) => Message[]; isFieldsTouched: ((nameList?: NamePath[], allFieldsTouched?: boolean) => boolean) & ((allFieldsTouched?: boolean) => boolean); isFieldTouched: (name: NamePath) => boolean; diff --git a/src/useForm.ts b/src/useForm.ts index 5799eec1d..5430cc911 100644 --- a/src/useForm.ts +++ b/src/useForm.ts @@ -16,6 +16,7 @@ import type { InternalNamePath, InternalValidateFields, InternalValidateOptions, + Message, Meta, NamePath, NotifyInfo, @@ -382,7 +383,7 @@ export class FormStore { }); }; - private getFieldError = (name: NamePath): string[] => { + private getFieldError = (name: NamePath): Message[] => { this.warningUnhooked(); const namePath = getNamePath(name); @@ -390,7 +391,7 @@ export class FormStore { return fieldError.errors; }; - private getFieldWarning = (name: NamePath): string[] => { + private getFieldWarning = (name: NamePath): Message[] => { this.warningUnhooked(); const namePath = getNamePath(name); @@ -877,7 +878,7 @@ export class FormStore { * Fill errors since `fields` may be replaced by controlled fields */ if (filedErrors) { - const cache = new NameMap(); + const cache = new NameMap(); filedErrors.forEach(({ name, errors }) => { cache.set(name, errors); }); @@ -971,8 +972,8 @@ export class FormStore { promise .then(() => ({ name: fieldNamePath, errors: [], warnings: [] })) .catch((ruleErrors: RuleError[]) => { - const mergedErrors: string[] = []; - const mergedWarnings: string[] = []; + const mergedErrors: Message[] = []; + const mergedWarnings: Message[] = []; ruleErrors.forEach?.(({ rule: { warningOnly }, errors }) => { if (warningOnly) { From e197dea5d228027ce0b03d77c341b562fced28fc Mon Sep 17 00:00:00 2001 From: Komoszek Date: Thu, 20 Nov 2025 22:12:05 +0100 Subject: [PATCH 2/3] fix: include ReactElement in ValidateMessage --- src/interface.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interface.ts b/src/interface.ts index ec2cb9393..c5b08a969 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -316,7 +316,7 @@ export type InternalFormInstance = Omit & { // eslint-disable-next-line @typescript-eslint/no-explicit-any export type EventArgs = any[]; -type ValidateMessage = string | (() => string); +type ValidateMessage = string | (() => string) | ReactElement; export interface ValidateMessages { default?: ValidateMessage; required?: ValidateMessage; From edb3477181bd9fae2e57728d5e9f726a66e066fb Mon Sep 17 00:00:00 2001 From: Komoszek Date: Thu, 20 Nov 2025 22:26:52 +0100 Subject: [PATCH 3/3] fix: reuse FieldError for returnPromise --- src/useForm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/useForm.ts b/src/useForm.ts index 5430cc911..cd7d0a992 100644 --- a/src/useForm.ts +++ b/src/useForm.ts @@ -1022,7 +1022,7 @@ export class FormStore { } return Promise.reject([]); }) - .catch((results: { name: InternalNamePath; errors: string[] }[]) => { + .catch((results: FieldError[]) => { const errorList = results.filter(result => result && result.errors.length); const errorMessage = errorList[0]?.errors?.[0]; return Promise.reject({