diff --git a/docs/demo/validateOnly.md b/docs/demo/validateOnly.md
new file mode 100644
index 00000000..2be92f39
--- /dev/null
+++ b/docs/demo/validateOnly.md
@@ -0,0 +1,3 @@
+## validateOnly
+
+
diff --git a/docs/examples/validateOnly.tsx b/docs/examples/validateOnly.tsx
new file mode 100644
index 00000000..df561830
--- /dev/null
+++ b/docs/examples/validateOnly.tsx
@@ -0,0 +1,76 @@
+/* eslint-disable react/prop-types, @typescript-eslint/consistent-type-imports */
+
+import React from 'react';
+import Form from 'rc-field-form';
+import type { FormInstance } from 'rc-field-form';
+import Input from './components/Input';
+import LabelField from './components/LabelField';
+
+function useSubmittable(form: FormInstance) {
+ const [submittable, setSubmittable] = React.useState(false);
+ const store = Form.useWatch([], form);
+
+ React.useEffect(() => {
+ form
+ .validateFields({
+ validateOnly: true,
+ })
+ .then(
+ () => {
+ setSubmittable(true);
+ },
+ () => {
+ setSubmittable(false);
+ },
+ );
+ }, [store]);
+
+ return submittable;
+}
+
+export default () => {
+ const [form] = Form.useForm();
+
+ const canSubmit = useSubmittable(form);
+
+ const onValidateOnly = async () => {
+ const result = await form.validateFields({
+ validateOnly: true,
+ });
+ console.log('Validate:', result);
+ };
+
+ return (
+ <>
+
+
+ >
+ );
+};
diff --git a/src/Field.tsx b/src/Field.tsx
index 806708fb..ce706ff9 100644
--- a/src/Field.tsx
+++ b/src/Field.tsx
@@ -10,7 +10,7 @@ import type {
NotifyInfo,
Rule,
Store,
- ValidateOptions,
+ InternalValidateOptions,
InternalFormInstance,
RuleObject,
StoreValue,
@@ -358,11 +358,13 @@ class Field extends React.Component implements F
}
};
- public validateRules = (options?: ValidateOptions): Promise => {
+ public validateRules = (options?: InternalValidateOptions): Promise => {
// We should fixed namePath & value to avoid developer change then by form function
const namePath = this.getNamePath();
const currentValue = this.getValue();
+ const { triggerName, validateOnly = false } = options || {};
+
// Force change to async to avoid rule OOD under renderProps field
const rootPromise = Promise.resolve().then(() => {
if (!this.mounted) {
@@ -370,7 +372,6 @@ class Field extends React.Component implements F
}
const { validateFirst = false, messageVariables } = this.props;
- const { triggerName } = (options || {}) as ValidateOptions;
let filteredRules = this.getRules();
if (triggerName) {
@@ -423,6 +424,10 @@ class Field extends React.Component implements F
return promise;
});
+ if (validateOnly) {
+ return rootPromise;
+ }
+
this.validatePromise = rootPromise;
this.dirty = true;
this.errors = EMPTY_ERRORS;
diff --git a/src/interface.ts b/src/interface.ts
index 3413d602..2fd74a4c 100644
--- a/src/interface.ts
+++ b/src/interface.ts
@@ -102,7 +102,7 @@ export interface FieldEntity {
isListField: () => boolean;
isList: () => boolean;
isPreserve: () => boolean;
- validateRules: (options?: ValidateOptions) => Promise;
+ validateRules: (options?: InternalValidateOptions) => Promise;
getMeta: () => Meta;
getNamePath: () => InternalNamePath;
getErrors: () => string[];
@@ -127,6 +127,18 @@ export interface RuleError {
}
export interface ValidateOptions {
+ /**
+ * Validate only and not trigger UI and Field status update
+ */
+ validateOnly?: boolean;
+}
+
+export type ValidateFields = {
+ (opt?: ValidateOptions): Promise;
+ (nameList?: NamePath[], opt?: ValidateOptions): Promise;
+};
+
+export interface InternalValidateOptions extends ValidateOptions {
triggerName?: string;
validateMessages?: ValidateMessages;
/**
@@ -136,11 +148,10 @@ export interface ValidateOptions {
recursive?: boolean;
}
-export type InternalValidateFields = (
- nameList?: NamePath[],
- options?: ValidateOptions,
-) => Promise;
-export type ValidateFields = (nameList?: NamePath[]) => Promise;
+export type InternalValidateFields = {
+ (options?: InternalValidateOptions): Promise;
+ (nameList?: NamePath[], options?: InternalValidateOptions): Promise;
+};
// >>>>>> Info
interface ValueUpdateInfo {
diff --git a/src/useForm.ts b/src/useForm.ts
index 1d497e85..f0af0bad 100644
--- a/src/useForm.ts
+++ b/src/useForm.ts
@@ -20,7 +20,7 @@ import type {
StoreValue,
ValidateErrorEntity,
ValidateMessages,
- ValidateOptions,
+ InternalValidateOptions,
ValuedNotifyInfo,
WatchCallBack,
} from './interface';
@@ -836,12 +836,19 @@ export class FormStore {
};
// =========================== Validate ===========================
- private validateFields: InternalValidateFields = (
- nameList?: NamePath[],
- options?: ValidateOptions,
- ) => {
+ private validateFields: InternalValidateFields = (arg1?: any, arg2?: any) => {
this.warningUnhooked();
+ let nameList: NamePath[];
+ let options: InternalValidateOptions;
+
+ if (Array.isArray(arg1) || typeof arg1 === 'string' || typeof arg2 === 'string') {
+ nameList = arg1;
+ options = arg2;
+ } else {
+ options = arg1;
+ }
+
const provideNameList = !!nameList;
const namePathList: InternalNamePath[] | undefined = provideNameList
? nameList.map(getNamePath)
diff --git a/src/utils/validateUtil.ts b/src/utils/validateUtil.ts
index 7270ac1e..dcff5ea3 100644
--- a/src/utils/validateUtil.ts
+++ b/src/utils/validateUtil.ts
@@ -3,7 +3,7 @@ import * as React from 'react';
import warning from 'rc-util/lib/warning';
import type {
InternalNamePath,
- ValidateOptions,
+ InternalValidateOptions,
RuleObject,
StoreValue,
RuleError,
@@ -31,7 +31,7 @@ async function validateRule(
name: string,
value: StoreValue,
rule: RuleObject,
- options: ValidateOptions,
+ options: InternalValidateOptions,
messageVariables?: Record,
): Promise {
const cloneRule = { ...rule };
@@ -123,7 +123,7 @@ export function validateRules(
namePath: InternalNamePath,
value: StoreValue,
rules: RuleObject[],
- options: ValidateOptions,
+ options: InternalValidateOptions,
validateFirst: boolean | 'parallel',
messageVariables?: Record,
) {
diff --git a/tests/validate.test.tsx b/tests/validate.test.tsx
index ef4a5e40..7d36aca8 100644
--- a/tests/validate.test.tsx
+++ b/tests/validate.test.tsx
@@ -1,11 +1,12 @@
import React, { useEffect } from 'react';
+import { render } from '@testing-library/react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import Form, { Field, useForm } from '../src';
import InfoField, { Input } from './common/InfoField';
import { changeValue, matchError, getField } from './common';
import timeout from './common/timeout';
-import type { ValidateMessages } from '@/interface';
+import type { FormInstance, ValidateMessages } from '../src/interface';
describe('Form.Validate', () => {
it('required', async () => {
@@ -867,4 +868,26 @@ describe('Form.Validate', () => {
expect(onMetaChange).toHaveBeenNthCalledWith(3, true);
expect(onMetaChange).toHaveBeenNthCalledWith(4, false);
});
+
+ it('validateOnly', async () => {
+ const formRef = React.createRef();
+ const { container } = render(
+ ,
+ );
+
+ // Validate only
+ const result = await formRef.current.validateFields({ validateOnly: true }).catch(e => e);
+ await timeout();
+ expect(result.errorFields).toHaveLength(1);
+ expect(container.querySelector('.errors').textContent).toBeFalsy();
+
+ // Normal validate
+ await formRef.current.validateFields().catch(e => e);
+ await timeout();
+ expect(container.querySelector('.errors').textContent).toEqual(`'test' is required`);
+ });
});