diff --git a/docs/demo/getFieldsName.md b/docs/demo/getFieldsName.md
new file mode 100644
index 000000000..919881dbf
--- /dev/null
+++ b/docs/demo/getFieldsName.md
@@ -0,0 +1,3 @@
+## getFieldsName
+
+
diff --git a/docs/examples/getFieldsName.tsx b/docs/examples/getFieldsName.tsx
new file mode 100644
index 000000000..934242b44
--- /dev/null
+++ b/docs/examples/getFieldsName.tsx
@@ -0,0 +1,25 @@
+import Form, { Field } from 'rc-field-form';
+import React from 'react';
+import Input from './components/Input';
+
+export default () => {
+ const [form] = Form.useForm();
+
+ return (
+
+ );
+};
diff --git a/src/FieldContext.ts b/src/FieldContext.ts
index d2b6f4d5d..123c86dc4 100644
--- a/src/FieldContext.ts
+++ b/src/FieldContext.ts
@@ -14,6 +14,7 @@ const Context = React.createContext({
getFieldsValue: warningFunc,
getFieldError: warningFunc,
getFieldWarning: warningFunc,
+ getFieldsName: warningFunc,
getFieldsError: warningFunc,
isFieldsTouched: warningFunc,
isFieldTouched: warningFunc,
diff --git a/src/hooks/useForm.ts b/src/hooks/useForm.ts
index c660f9a23..42fa167da 100644
--- a/src/hooks/useForm.ts
+++ b/src/hooks/useForm.ts
@@ -87,6 +87,7 @@ export class FormStore {
getFieldsValue: this.getFieldsValue,
getFieldError: this.getFieldError,
getFieldWarning: this.getFieldWarning,
+ getFieldsName: this.getFieldsName,
getFieldsError: this.getFieldsError,
isFieldsTouched: this.isFieldsTouched,
isFieldTouched: this.isFieldTouched,
@@ -345,6 +346,11 @@ export class FormStore {
return mergedValues;
};
+ private getFieldsName = (): InternalNamePath[] => {
+ this.warningUnhooked();
+ return this.getFieldEntities(true).map(entity => entity.getNamePath());
+ };
+
private getFieldValue = (name: NamePath) => {
this.warningUnhooked();
diff --git a/src/interface.ts b/src/interface.ts
index 1af70ea59..584e21f8d 100644
--- a/src/interface.ts
+++ b/src/interface.ts
@@ -272,6 +272,7 @@ export interface FormInstance {
getFieldError: (name: NamePath) => string[];
getFieldsError: (nameList?: NamePath[]) => FieldError[];
getFieldWarning: (name: NamePath) => string[];
+ getFieldsName: () => NamePath[];
isFieldsTouched: ((nameList?: NamePath[], allFieldsTouched?: boolean) => boolean) &
((allFieldsTouched?: boolean) => boolean);
isFieldTouched: (name: NamePath) => boolean;
diff --git a/tests/getFieldsName.test.tsx b/tests/getFieldsName.test.tsx
new file mode 100644
index 000000000..fdbfb7059
--- /dev/null
+++ b/tests/getFieldsName.test.tsx
@@ -0,0 +1,98 @@
+import { render } from '@testing-library/react';
+import React from 'react';
+import Form, { Field, List } from '../src';
+import type { FormInstance } from '../src';
+import { Input } from './common/InfoField';
+
+describe('getFieldsName', () => {
+ it('returns empty array when no named fields', () => {
+ const formRef = React.createRef();
+ render();
+ expect(formRef.current!.getFieldsName()).toEqual([]);
+ });
+
+ it('returns name paths of registered fields', () => {
+ const formRef = React.createRef();
+ render(
+ ,
+ );
+ expect(formRef.current!.getFieldsName()).toEqual([['username'], ['profile', 'email']]);
+ });
+
+ it('excludes field without name', () => {
+ const formRef = React.createRef();
+ render(
+ ,
+ );
+ expect(formRef.current!.getFieldsName()).toEqual([['a']]);
+ });
+
+ it('includes one entry per Field with the same name', () => {
+ const formRef = React.createRef();
+ render(
+ ,
+ );
+ expect(formRef.current!.getFieldsName()).toEqual([['x'], ['x']]);
+ });
+
+ it('updates when field unmounts', () => {
+ const formRef = React.createRef();
+ const Demo = ({ show }: { show: boolean }) => (
+
+ );
+ const { rerender } = render();
+ expect(formRef.current!.getFieldsName()).toEqual([['keep'], ['toggle']]);
+ rerender();
+ expect(formRef.current!.getFieldsName()).toEqual([['keep']]);
+ });
+
+ it('includes Form.List item fields', () => {
+ const formRef = React.createRef();
+ render(
+ ,
+ );
+ expect(formRef.current!.getFieldsName()).toEqual([
+ ['list', 0],
+ ['list', 1],
+ ['list'],
+ ]);
+ });
+});