react-form-manage 1.1.0-beta.2 → 1.1.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,40 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.1.0-beta.4] - 2026-03-04
6
+
7
+ ### Performance & Code Quality
8
+
9
+ - **Performance Optimization**: Added `useShallow` to `useForm` hook's formInstance selector
10
+ - Prevents unnecessary re-renders when form instances array changes
11
+ - Uses shallow comparison for better performance in form instance lookup
12
+ - **Code Cleanup**: Removed debug console.log statements
13
+ - Cleaned up FormItem component logging
14
+ - Cleaned up useFormItemControl cleanup logging
15
+ - **Code Consistency**: Improved variable naming in Form provider
16
+ - Changed `externalFormInstance` references to unified `formInstance` variable
17
+ - Better code readability and maintainability
18
+
19
+ ### Technical Details
20
+
21
+ - `useShallow` from Zustand improves selector performance by using shallow equality check
22
+ - Removed unused `useEffect` import from FormItem component
23
+ - All debug logs removed for production-ready code
24
+
25
+ ## [1.1.0-beta.3] - 2026-03-03
26
+
27
+ ### Type Fix
28
+
29
+ - **PublicFormInstance.setFieldValues**: Corrected options parameter type from `SetFieldValueOptions` to `OnChangeOptions`
30
+ - `setFieldValues` does not support `deepTrigger` option (only available in `setFieldValue`)
31
+ - Now correctly reflects the actual implementation
32
+
33
+ ### Technical Details
34
+
35
+ - `SetFieldValueOptions` extends `OnChangeOptions` with additional `deepTrigger?: boolean`
36
+ - `setFieldValue` (singular): Supports `deepTrigger` for nested field updates
37
+ - `setFieldValues` (plural): Batch updates without `deepTrigger` support
38
+
5
39
  ## [1.1.0-beta.2] - 2026-03-03
6
40
 
7
41
  ### Type Improvements
@@ -1,11 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { isNil } from "lodash";
3
- import { cloneElement, Fragment, useEffect, useRef, useState } from "react";
3
+ import { cloneElement, Fragment, useRef, useState } from "react";
4
4
  import { v4 } from "uuid";
5
5
  import useFormItemControl from "../../hooks/useFormItemControl";
6
6
  function BaseFormItem({ children, name, elementRef: elRef, valuePropName, getValueFromEvent, onChange, value, isDirty, errors, onFocus, state, submitState, isTouched }) {
7
- useEffect(() => {
8
- }, [value]);
9
7
  return cloneElement(children, {
10
8
  name,
11
9
  ref: elRef,
@@ -392,17 +392,17 @@ const InternalForm = function Form({ children, formName: externalFormName, form:
392
392
  submitState: "idle"
393
393
  });
394
394
  const formInstance = externalFormInstance != null ? externalFormInstance : {};
395
- if (externalFormInstance) {
396
- externalFormInstance.setFieldValue = setFieldValue;
397
- externalFormInstance.setFieldValues = setFieldValues;
398
- externalFormInstance.getFieldValue = getFieldValue;
399
- externalFormInstance.getFieldValues = getFieldValues;
400
- externalFormInstance.setFieldFocus = setFieldFocus;
401
- externalFormInstance.getFieldErrors = getFieldErrors;
402
- externalFormInstance.submit = runSubmit;
403
- externalFormInstance.submitAsync = runSubmitAsync;
404
- externalFormInstance.resetFields = resetFields;
405
- externalFormInstance.formName = formName;
395
+ if (formInstance) {
396
+ formInstance.setFieldValue = setFieldValue;
397
+ formInstance.setFieldValues = setFieldValues;
398
+ formInstance.getFieldValue = getFieldValue;
399
+ formInstance.getFieldValues = getFieldValues;
400
+ formInstance.setFieldFocus = setFieldFocus;
401
+ formInstance.getFieldErrors = getFieldErrors;
402
+ formInstance.submit = runSubmit;
403
+ formInstance.submitAsync = runSubmitAsync;
404
+ formInstance.resetFields = resetFields;
405
+ formInstance.formName = formName;
406
406
  }
407
407
  setFormInstance(formInstance);
408
408
  return () => {
@@ -436,9 +436,9 @@ function useForm(formNameOrFormInstance) {
436
436
  return isNil(formNameOrFormInstance) ? formContext == null ? void 0 : formContext.formName : typeof formNameOrFormInstance === "object" && formNameOrFormInstance !== null ? formNameOrFormInstance.formName : formNameOrFormInstance;
437
437
  });
438
438
  const [initFormName] = useState(() => targetFormName != null ? targetFormName : v4());
439
- const formInstance = useFormStore((state) => {
439
+ const formInstance = useFormStore(useShallow((state) => {
440
440
  return state.formInstances.find((i) => i.formName === initFormName);
441
- });
441
+ }));
442
442
  const getFormInstance = useFormStore((state) => {
443
443
  return state.getFormInstance;
444
444
  });
@@ -0,0 +1,3 @@
1
+ type Props = {};
2
+ declare function SelfTestGetForm({}: Props): import("react/jsx-runtime").JSX.Element;
3
+ export default SelfTestGetForm;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect } from "react";
3
+ import Input from "../components/Input";
4
+ import Form from "../providers/Form";
5
+ function SelfTestGetForm({}) {
6
+ const [form] = Form.useForm("selfTestGetForm");
7
+ useEffect(() => {
8
+ setTimeout(() => {
9
+ if (form) {
10
+ form.setFieldValue("field1", "Value 1");
11
+ }
12
+ }, 1e3);
13
+ }, [form]);
14
+ return _jsxs(Form, { formName: "selfTestGetForm", children: [_jsx(Form.Item, { name: "field1", label: "Field 1", children: _jsx(Input, {}) }), _jsx(Form.Item, { name: "field2", label: "Field 2", children: _jsx(Input, {}) })] });
15
+ }
16
+ var stdin_default = SelfTestGetForm;
17
+ export {
18
+ stdin_default as default
19
+ };
@@ -73,8 +73,13 @@ function TestCase8_TypeSafetyImprovements() {
73
73
  render: (errors) => errors.length > 0 ? _jsx("div", { children: errors.map((err, idx) => _jsx("div", { style: { fontSize: 12 }, children: err.message }, idx)) }) : _jsx(Text, { type: "secondary", children: "No errors" })
74
74
  }
75
75
  ];
76
- return _jsxs("div", { style: { padding: 24 }, children: [_jsx(Title, { level: 3, children: "TestCase8: Type Safety Improvements" }), _jsxs(Paragraph, { children: [_jsx(Text, { strong: true, children: "Test Focus:" }), " Verify type improvements trong PublicFormInstance"] }), _jsx(Paragraph, { children: _jsxs(Text, { type: "secondary", children: ["Test case n\xE0y validate c\xE1c type changes m\u1EDBi: dynamic field names ", `(keyof T |
77
- (string & {}))`, ", getFieldErrors return type, v\xE0 submitAsync Promise", "<", "void", ">", "."] }) }), _jsx(Card, { title: "\u{1F4DD} Type Information", style: { marginBottom: 24 }, children: _jsxs("div", { style: { background: "#f5f5f5", padding: 16, fontFamily: "monospace", fontSize: 12 }, children: [_jsxs("div", { children: [_jsx(Text, { strong: true, children: "Form Type:" }), " ", _jsxs(Text, { code: true, children: ["StrictFormValues = ", `{ username: string; email: string; age: number }`] })] }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Text, { strong: true, children: "New Types:" }) }), _jsxs("ul", { style: { marginLeft: 20, marginTop: 4 }, children: [_jsxs("li", { children: [_jsx(Text, { code: true, children: `name: keyof T | (string & {})` }), " - Supports dynamic fields"] }), _jsx("li", { children: _jsx(Text, { code: true, children: `getFieldErrors: (...) => Array<{ name: string; errors: FormFieldError[] }>` }) }), _jsx("li", { children: _jsx(Text, { code: true, children: `submitAsync: (...) => Promise<void>` }) })] })] }) }), _jsxs("div", { style: { display: "flex", gap: 24 }, children: [_jsx("div", { style: { flex: 1 }, children: _jsx(Card, { title: "\u{1F3AE} Test Controls", children: _jsxs(Space, { direction: "vertical", style: { width: "100%" }, size: "middle", children: [_jsxs("div", { children: [_jsx(Text, { strong: true, children: "1. Typed Field Names:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Button, { onClick: handleSetTypedFields, type: "primary", block: true, children: "Set Typed Fields (username, email, age)" }) })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "2. Dynamic Field Names:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Button, { onClick: handleSetDynamicFields, block: true, children: "Set Dynamic Fields (not in type)" }) })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "3. Get Values:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Button, { onClick: handleGetValues, block: true, children: "Get All Field Values" }) })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "4. Get Field Errors:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Button, { onClick: handleGetFieldErrors, block: true, children: "Get Field Errors (New Type)" }) })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "5. Multiple Values:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Button, { onClick: handleGetMultipleValues, block: true, children: "Get Multiple Values" }) })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "6. Submit Async:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsxs(Button, { onClick: handleSubmitAsync, type: "dashed", block: true, children: ["Test submitAsync (Promise", "<", "void", ">", ")"] }) })] }), _jsxs(Space, { style: { width: "100%", justifyContent: "space-between" }, children: [_jsx(Button, { onClick: clearLogs, size: "small", children: "Clear Logs" }), _jsx(Button, { onClick: clearErrors, size: "small", children: "Clear Errors" })] })] }) }) }), _jsx("div", { style: { flex: 1 }, children: _jsx(Card, { title: "\u{1F4CB} Action Logs", children: _jsx("div", { style: {
76
+ return _jsxs("div", { style: { padding: 24 }, children: [_jsx(Title, { level: 3, children: "TestCase8: Type Safety Improvements" }), _jsxs(Paragraph, { children: [_jsx(Text, { strong: true, children: "Test Focus:" }), " Verify type improvements trong PublicFormInstance"] }), _jsx(Paragraph, { children: _jsxs(Text, { type: "secondary", children: ["Test case n\xE0y validate c\xE1c type changes m\u1EDBi: dynamic field names", " ", `(keyof T |
77
+ (string & {}))`, ", getFieldErrors return type, v\xE0 submitAsync Promise", "<", "void", ">", "."] }) }), _jsx(Card, { title: "\u{1F4DD} Type Information", style: { marginBottom: 24 }, children: _jsxs("div", { style: {
78
+ background: "#f5f5f5",
79
+ padding: 16,
80
+ fontFamily: "monospace",
81
+ fontSize: 12
82
+ }, children: [_jsxs("div", { children: [_jsx(Text, { strong: true, children: "Form Type:" }), " ", _jsxs(Text, { code: true, children: ["StrictFormValues =", " ", `{ username: string; email: string; age: number }`] })] }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Text, { strong: true, children: "New Types:" }) }), _jsxs("ul", { style: { marginLeft: 20, marginTop: 4 }, children: [_jsxs("li", { children: [_jsx(Text, { code: true, children: `name: keyof T | (string & {})` }), " - Supports dynamic fields"] }), _jsx("li", { children: _jsx(Text, { code: true, children: `getFieldErrors: (...) => Array<{ name: string; errors: FormFieldError[] }>` }) }), _jsx("li", { children: _jsx(Text, { code: true, children: `submitAsync: (...) => Promise<void>` }) })] })] }) }), _jsxs("div", { style: { display: "flex", gap: 24 }, children: [_jsx("div", { style: { flex: 1 }, children: _jsx(Card, { title: "\u{1F3AE} Test Controls", children: _jsxs(Space, { direction: "vertical", style: { width: "100%" }, size: "middle", children: [_jsxs("div", { children: [_jsx(Text, { strong: true, children: "1. Typed Field Names:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Button, { onClick: handleSetTypedFields, type: "primary", block: true, children: "Set Typed Fields (username, email, age)" }) })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "2. Dynamic Field Names:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Button, { onClick: handleSetDynamicFields, block: true, children: "Set Dynamic Fields (not in type)" }) })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "3. Get Values:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Button, { onClick: handleGetValues, block: true, children: "Get All Field Values" }) })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "4. Get Field Errors:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Button, { onClick: handleGetFieldErrors, block: true, children: "Get Field Errors (New Type)" }) })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "5. Multiple Values:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsx(Button, { onClick: handleGetMultipleValues, block: true, children: "Get Multiple Values" }) })] }), _jsxs("div", { children: [_jsx(Text, { strong: true, children: "6. Submit Async:" }), _jsx("div", { style: { marginTop: 8 }, children: _jsxs(Button, { onClick: handleSubmitAsync, type: "dashed", block: true, children: ["Test submitAsync (Promise", "<", "void", ">", ")"] }) })] }), _jsxs(Space, { style: { width: "100%", justifyContent: "space-between" }, children: [_jsx(Button, { onClick: clearLogs, size: "small", children: "Clear Logs" }), _jsx(Button, { onClick: clearErrors, size: "small", children: "Clear Errors" })] })] }) }) }), _jsx("div", { style: { flex: 1 }, children: _jsx(Card, { title: "\u{1F4CB} Action Logs", children: _jsx("div", { style: {
78
83
  height: 400,
79
84
  overflow: "auto",
80
85
  background: "#fafafa",
@@ -90,7 +95,7 @@ function TestCase8_TypeSafetyImprovements() {
90
95
  ], children: _jsx("input", { type: "email", placeholder: "Email", style: { padding: 8, width: "100%" } }) }), _jsx(Form.Item, { name: "age", label: "Age", rules: [
91
96
  { required: true, message: "Age is required" },
92
97
  { min: 18, message: "Must be at least 18" }
93
- ], children: _jsx("input", { type: "number", placeholder: "Age", style: { padding: 8, width: "100%" } }) }), _jsx(Button, { type: "primary", htmlType: "submit", children: "Submit Form" })] }) }), fieldErrors.length > 0 && _jsxs(Card, { title: "\u{1F50D} Field Errors (getFieldErrors Result)", style: { marginTop: 24 }, children: [_jsx(Table, { dataSource: fieldErrors, columns: errorColumns, rowKey: "name", pagination: false, size: "small" }), _jsx("div", { style: { marginTop: 16, padding: 12, background: "#f0f0f0" }, children: _jsxs(Text, { type: "secondary", children: ["\u2705 Return type:", " ", _jsx(Text, { code: true, children: `Array<{ name: string; errors: FormFieldError[] }>` })] }) })] }), _jsxs("div", { style: { marginTop: 24, padding: 16, background: "#f5f5f5" }, children: [_jsx(Title, { level: 5, children: "Expected Results:" }), _jsxs("ul", { children: [_jsx("li", { children: '\u2705 Click "Set Typed Fields" \u2192 Values set for username, email, age' }), _jsx("li", { children: '\u2705 Click "Set Dynamic Fields" \u2192 Dynamic fields work (not in StrictFormValues type)' }), _jsx("li", { children: '\u2705 Click "Get All Field Values" \u2192 Shows all field values including dynamic' }), _jsx("li", { children: '\u2705 Click "Get Field Errors" \u2192 Returns array with name and errors properties' }), _jsxs("li", { children: ['\u2705 Click "Get Multiple Values" \u2192 Returns array of ', `{ name, value }`, " objects"] }), _jsxs("li", { children: ['\u2705 Click "Test submitAsync" \u2192 Returns Promise', "<", "void", ">", " (result is undefined)"] }), _jsx("li", { children: "\u2705 Submit form with empty fields \u2192 Validation errors appear in getFieldErrors" }), _jsx("li", { children: "\u2705 Fill form and submit \u2192 onFinish triggered, no validation errors" })] })] }), _jsxs("div", { style: {
98
+ ], children: _jsx("input", { type: "number", placeholder: "Age", style: { padding: 8, width: "100%" } }) }), _jsx(Button, { type: "primary", htmlType: "submit", children: "Submit Form" })] }) }), fieldErrors.length > 0 && _jsxs(Card, { title: "\u{1F50D} Field Errors (getFieldErrors Result)", style: { marginTop: 24 }, children: [_jsx(Table, { dataSource: fieldErrors, columns: errorColumns, rowKey: "name", pagination: false, size: "small" }), _jsx("div", { style: { marginTop: 16, padding: 12, background: "#f0f0f0" }, children: _jsxs(Text, { type: "secondary", children: ["\u2705 Return type:", " ", _jsx(Text, { code: true, children: `Array<{ name: string; errors: FormFieldError[] }>` })] }) })] }), _jsxs("div", { style: { marginTop: 24, padding: 16, background: "#f5f5f5" }, children: [_jsx(Title, { level: 5, children: "Expected Results:" }), _jsxs("ul", { children: [_jsx("li", { children: '\u2705 Click "Set Typed Fields" \u2192 Values set for username, email, age' }), _jsx("li", { children: '\u2705 Click "Set Dynamic Fields" \u2192 Dynamic fields work (not in StrictFormValues type)' }), _jsx("li", { children: '\u2705 Click "Get All Field Values" \u2192 Shows all field values including dynamic' }), _jsx("li", { children: '\u2705 Click "Get Field Errors" \u2192 Returns array with name and errors properties' }), _jsxs("li", { children: ['\u2705 Click "Get Multiple Values" \u2192 Returns array of', " ", `{ name, value }`, " objects"] }), _jsxs("li", { children: ['\u2705 Click "Test submitAsync" \u2192 Returns Promise', "<", "void", ">", " (result is undefined)"] }), _jsx("li", { children: "\u2705 Submit form with empty fields \u2192 Validation errors appear in getFieldErrors" }), _jsx("li", { children: "\u2705 Fill form and submit \u2192 onFinish triggered, no validation errors" })] })] }), _jsxs("div", { style: {
94
99
  marginTop: 16,
95
100
  padding: 16,
96
101
  background: "#e6f7ff",
@@ -1,4 +1,5 @@
1
1
  import type { SUBMIT_STATE } from "../constants/form";
2
+ import { OnChangeOptions } from "../hooks/useFormItemControl";
2
3
  import { SetFieldValueOptions } from "../providers/Form";
3
4
  import type { GetConstantType } from "./util";
4
5
  export type FormValues<T = any> = T;
@@ -36,7 +37,7 @@ export interface PublicFormInstance<T = any> {
36
37
  submit: (values?: T) => void;
37
38
  submitAsync: (values?: T) => Promise<void>;
38
39
  setFieldValue: (name: keyof T | (string & {}), value: any, options?: SetFieldValueOptions) => void;
39
- setFieldValues: (values: Partial<T>, options?: SetFieldValueOptions) => void;
40
+ setFieldValues: (values: Partial<T>, options?: OnChangeOptions) => void;
40
41
  getFieldValue: (name: keyof T | (string & {})) => any;
41
42
  getFieldValues: (names?: Array<keyof T | (string & {})>) => Array<{
42
43
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-form-manage",
3
- "version": "1.1.0-beta.2",
3
+ "version": "1.1.0-beta.4",
4
4
  "description": "Lightweight React form management with list and listener support.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/App.tsx CHANGED
@@ -1,9 +1,7 @@
1
1
  import Form from "./providers/Form";
2
2
 
3
3
  import { Form as AntdForm } from "antd";
4
- import TestSetValueInEffect from "./test/testSetValue/TestSetValueInEffect";
5
- import FormInstanceTestSuite from "./test/testFormInstance";
6
- import TestFormInstance from "./test/testFormInstance";
4
+ import SelfTestGetForm from "./test/SelfTestGetForm";
7
5
 
8
6
  function TestFormWatch() {
9
7
  const watchValue = Form.useWatch("numericCode");
@@ -48,7 +46,9 @@ const App = () => {
48
46
  {/* <TestSetValueIndex /> */}
49
47
  {/* <TestSetValueInEffect /> */}
50
48
 
51
- <FormInstanceTestSuite />
49
+ {/* <FormInstanceTestSuite /> */}
50
+
51
+ <SelfTestGetForm />
52
52
  </div>
53
53
  );
54
54
  };
@@ -1,6 +1,6 @@
1
1
  import { isNil } from "lodash";
2
2
  import type { ComponentProps, FC, ReactElement } from "react";
3
- import { cloneElement, Fragment, useEffect, useRef, useState } from "react";
3
+ import { cloneElement, Fragment, useRef, useState } from "react";
4
4
  import { v4 } from "uuid";
5
5
  import useFormItemControl from "../../hooks/useFormItemControl";
6
6
  import type { ValidationRule } from "../../types/public";
@@ -47,9 +47,9 @@ function BaseFormItem({
47
47
  submitState,
48
48
  isTouched,
49
49
  }: any) {
50
- useEffect(() => {
51
- console.log("Base Form Item: ", { name, value });
52
- }, [value]);
50
+ // useEffect(() => {
51
+ // console.log("Base Form Item: ", { name, value });
52
+ // }, [value]);
53
53
  return cloneElement(children, {
54
54
  name,
55
55
  ref: elRef,
@@ -598,7 +598,7 @@ export default function useFormItemControl<T = any>({
598
598
  return () => {
599
599
  revokeListener(formItemId, (listener, same) => {
600
600
  if (!same.length) {
601
- console.log("Trigger after revoke: ", listener, same);
601
+ // console.log("Trigger after revoke: ", listener, same);
602
602
  clearObjKeyItem(listener.formName, listener.name);
603
603
  }
604
604
  });
@@ -768,17 +768,17 @@ const InternalForm: FormDeclearation = function Form<T = any>({
768
768
  });
769
769
 
770
770
  const formInstance = externalFormInstance ?? ({} as PublicFormInstance<T>);
771
- if (externalFormInstance) {
772
- externalFormInstance.setFieldValue = setFieldValue;
773
- externalFormInstance.setFieldValues = setFieldValues;
774
- externalFormInstance.getFieldValue = getFieldValue;
775
- externalFormInstance.getFieldValues = getFieldValues;
776
- externalFormInstance.setFieldFocus = setFieldFocus;
777
- externalFormInstance.getFieldErrors = getFieldErrors;
778
- externalFormInstance.submit = runSubmit;
779
- externalFormInstance.submitAsync = runSubmitAsync;
780
- externalFormInstance.resetFields = resetFields;
781
- externalFormInstance.formName = formName;
771
+ if (formInstance) {
772
+ formInstance.setFieldValue = setFieldValue;
773
+ formInstance.setFieldValues = setFieldValues;
774
+ formInstance.getFieldValue = getFieldValue;
775
+ formInstance.getFieldValues = getFieldValues;
776
+ formInstance.setFieldFocus = setFieldFocus;
777
+ formInstance.getFieldErrors = getFieldErrors;
778
+ formInstance.submit = runSubmit;
779
+ formInstance.submitAsync = runSubmitAsync;
780
+ formInstance.resetFields = resetFields;
781
+ formInstance.formName = formName;
782
782
  }
783
783
 
784
784
  setFormInstance(formInstance);
@@ -859,9 +859,11 @@ export function useForm<T = any>(
859
859
  const [initFormName] = useState(() => targetFormName ?? v4());
860
860
 
861
861
  // Lấy formInstance từ store dựa trên initFormName
862
- const formInstance = useFormStore((state) => {
863
- return state.formInstances.find((i) => i.formName === initFormName);
864
- }) as PublicFormInstance<T> | undefined;
862
+ const formInstance = useFormStore(
863
+ useShallow((state) => {
864
+ return state.formInstances.find((i) => i.formName === initFormName);
865
+ }),
866
+ ) as PublicFormInstance<T> | undefined;
865
867
 
866
868
  const getFormInstance = useFormStore((state) => {
867
869
  return state.getFormInstance;
@@ -0,0 +1,30 @@
1
+ import { useEffect } from "react";
2
+ import Input from "../components/Input";
3
+ import Form from "../providers/Form";
4
+
5
+ type Props = {};
6
+
7
+ function SelfTestGetForm({}: Props) {
8
+ const [form] = Form.useForm("selfTestGetForm");
9
+
10
+ useEffect(() => {
11
+ setTimeout(() => {
12
+ if (form) {
13
+ console.log("Form", form);
14
+ form.setFieldValue("field1", "Value 1");
15
+ }
16
+ }, 1000);
17
+ }, [form]);
18
+ return (
19
+ <Form formName="selfTestGetForm">
20
+ <Form.Item name="field1" label="Field 1">
21
+ <Input />
22
+ </Form.Item>
23
+ <Form.Item name="field2" label="Field 2">
24
+ <Input />
25
+ </Form.Item>
26
+ </Form>
27
+ );
28
+ }
29
+
30
+ export default SelfTestGetForm;
@@ -6,13 +6,13 @@ const { Title, Paragraph, Text } = Typography;
6
6
 
7
7
  /**
8
8
  * TestCase7: Set Value in useEffect with Timeout
9
- *
9
+ *
10
10
  * Mô tả:
11
11
  * - Test scenario: setValue được gọi trong useEffect với setTimeout
12
12
  * - Form instance được tạo trước và truyền vào Form component
13
13
  * - Timeout được khởi tạo ngay khi component mount
14
14
  * - Test xem setValue có hoạt động sau khi timeout trigger
15
- *
15
+ *
16
16
  * Expected Behavior:
17
17
  * - setTimeout được tạo ngay khi component mount
18
18
  * - Sau thời gian timeout, setValue được gọi và hoạt động bình thường
@@ -41,11 +41,11 @@ function FormWithTimeout({ delay = 2000 }: { delay?: number }) {
41
41
 
42
42
  const timer = setTimeout(() => {
43
43
  addLog("🔔 Timeout triggered! Calling setFieldValue...");
44
-
44
+
45
45
  form.setFieldValue("username", "admin_from_timeout");
46
46
  form.setFieldValue("email", "admin@timeout.com");
47
47
  form.setFieldValue("status", "loaded");
48
-
48
+
49
49
  addLog("✅ setFieldValue completed successfully");
50
50
  setTimeoutTriggered(true);
51
51
  }, delay);
@@ -185,9 +185,10 @@ export default function TestCase7_SetValueInEffect() {
185
185
 
186
186
  <Paragraph>
187
187
  <Text type="secondary">
188
- Test case này verify rằng form instance methods vẫn hoạt động bình thường khi
189
- được gọi từ async callbacks (setTimeout). Timeout được tạo ngay khi component
190
- mount, và sau khi trigger, setValue phải hoạt động chính xác.
188
+ Test case này verify rằng form instance methods vẫn hoạt động bình
189
+ thường khi được gọi từ async callbacks (setTimeout). Timeout được tạo
190
+ ngay khi component mount, và sau khi trigger, setValue phải hoạt động
191
+ chính xác.
191
192
  </Text>
192
193
  </Paragraph>
193
194
 
@@ -239,8 +240,8 @@ export default function TestCase7_SetValueInEffect() {
239
240
  }}
240
241
  >
241
242
  <Text>
242
- 💡 <strong>Tip:</strong> Try unmounting the component before timeout triggers
243
- to test cleanup behavior
243
+ 💡 <strong>Tip:</strong> Try unmounting the component before timeout
244
+ triggers to test cleanup behavior
244
245
  </Text>
245
246
  </div>
246
247
  </Card>
@@ -253,22 +254,24 @@ export default function TestCase7_SetValueInEffect() {
253
254
  <Title level={5}>Expected Results:</Title>
254
255
  <ul>
255
256
  <li>
256
- ✅ Component mounts → Logs show "setTimeout created with {delay}ms delay"
257
+ ✅ Component mounts → Logs show "setTimeout created with {delay}ms
258
+ delay"
257
259
  </li>
258
260
  <li>
259
- ✅ After {delay}ms → Logs show "Timeout triggered!" and "setFieldValue
260
- completed successfully"
261
+ ✅ After {delay}ms → Logs show "Timeout triggered!" and
262
+ "setFieldValue completed successfully"
261
263
  </li>
262
264
  <li>✅ Form values update to: username="admin_from_timeout", etc.</li>
263
265
  <li>✅ Click "Manual Set" → Values change immediately</li>
264
266
  <li>✅ Click "Reset" → Form resets to initialValues</li>
265
267
  <li>
266
- ✅ Click "Unmount" before timeout → Logs show cleanup, no error when timeout
267
- would trigger
268
+ ✅ Click "Unmount" before timeout → Logs show cleanup, no error when
269
+ timeout would trigger
268
270
  </li>
269
271
  <li>✅ Change delay and remount → New timeout with new delay</li>
270
272
  <li>
271
- ✅ Submit form after timeout → Form submission works with updated values
273
+ ✅ Submit form after timeout → Form submission works with updated
274
+ values
272
275
  </li>
273
276
  </ul>
274
277
  </div>
@@ -284,21 +287,24 @@ export default function TestCase7_SetValueInEffect() {
284
287
  <Title level={5}>Use Cases:</Title>
285
288
  <ul style={{ marginBottom: 0 }}>
286
289
  <li>
287
- <strong>Async Data Loading:</strong> Load form data from API after component
288
- mount
290
+ <strong>Async Data Loading:</strong> Load form data from API after
291
+ component mount
289
292
  </li>
290
293
  <li>
291
- <strong>Delayed Initialization:</strong> Initialize form values after some
292
- delay
294
+ <strong>Delayed Initialization:</strong> Initialize form values
295
+ after some delay
293
296
  </li>
294
297
  <li>
295
- <strong>Auto-save:</strong> Save form values periodically using setInterval
298
+ <strong>Auto-save:</strong> Save form values periodically using
299
+ setInterval
296
300
  </li>
297
301
  <li>
298
- <strong>Debounced Updates:</strong> Update form values with debounce/throttle
302
+ <strong>Debounced Updates:</strong> Update form values with
303
+ debounce/throttle
299
304
  </li>
300
305
  <li>
301
- <strong>WebSocket Updates:</strong> Update form when receiving real-time data
306
+ <strong>WebSocket Updates:</strong> Update form when receiving
307
+ real-time data
302
308
  </li>
303
309
  </ul>
304
310
  </div>
@@ -6,13 +6,13 @@ const { Title, Paragraph, Text } = Typography;
6
6
 
7
7
  /**
8
8
  * TestCase8: Type Safety Improvements
9
- *
9
+ *
10
10
  * Mô tả:
11
11
  * - Test type improvements trong PublicFormInstance
12
12
  * - Dynamic field names: keyof T | (string & {})
13
13
  * - getFieldErrors return type: Array<{ name: string; errors: FormFieldError[] }>
14
14
  * - submitAsync return type: Promise<void>
15
- *
15
+ *
16
16
  * Expected Behavior:
17
17
  * - Dynamic field names hoạt động với typed và untyped fields
18
18
  * - getFieldErrors trả về array với proper structure
@@ -60,8 +60,10 @@ export default function TestCase8_TypeSafetyImprovements() {
60
60
  const email = form.getFieldValue("email");
61
61
  const age = form.getFieldValue("age");
62
62
  const dynamic = form.getFieldValue("dynamicField1" as any);
63
-
64
- addLog(`📖 Get values: username=${username}, email=${email}, age=${age}, dynamic=${dynamic}`);
63
+
64
+ addLog(
65
+ `📖 Get values: username=${username}, email=${email}, age=${age}, dynamic=${dynamic}`,
66
+ );
65
67
  };
66
68
 
67
69
  // Test 4: Get field errors (NEW TYPE)
@@ -78,7 +80,9 @@ export default function TestCase8_TypeSafetyImprovements() {
78
80
  addLog("🚀 Calling submitAsync...");
79
81
  const result = await form.submitAsync();
80
82
  // result should be void (undefined)
81
- addLog(`✅ submitAsync completed. Result type: ${typeof result} (should be undefined)`);
83
+ addLog(
84
+ `✅ submitAsync completed. Result type: ${typeof result} (should be undefined)`,
85
+ );
82
86
  console.log("submitAsync result:", result);
83
87
  } catch (error) {
84
88
  addLog(`❌ submitAsync error: ${error}`);
@@ -112,7 +116,11 @@ export default function TestCase8_TypeSafetyImprovements() {
112
116
  dataIndex: "errors",
113
117
  key: "count",
114
118
  render: (errors: any[]) => (
115
- <Badge count={errors.length} showZero color={errors.length > 0 ? "red" : "green"} />
119
+ <Badge
120
+ count={errors.length}
121
+ showZero
122
+ color={errors.length > 0 ? "red" : "green"}
123
+ />
116
124
  ),
117
125
  },
118
126
  {
@@ -139,32 +147,48 @@ export default function TestCase8_TypeSafetyImprovements() {
139
147
  <Title level={3}>TestCase8: Type Safety Improvements</Title>
140
148
 
141
149
  <Paragraph>
142
- <Text strong>Test Focus:</Text> Verify type improvements trong PublicFormInstance
150
+ <Text strong>Test Focus:</Text> Verify type improvements trong
151
+ PublicFormInstance
143
152
  </Paragraph>
144
153
 
145
154
  <Paragraph>
146
155
  <Text type="secondary">
147
- Test case này validate các type changes mới: dynamic field names {`(keyof T |
148
- (string & {}))`}, getFieldErrors return type, và submitAsync Promise{'<'}void{'>'}.
156
+ Test case này validate các type changes mới: dynamic field names{" "}
157
+ {`(keyof T |
158
+ (string & {}))`}
159
+ , getFieldErrors return type, và submitAsync Promise{"<"}void{">"}.
149
160
  </Text>
150
161
  </Paragraph>
151
162
 
152
163
  {/* Type Info Panel */}
153
164
  <Card title="📝 Type Information" style={{ marginBottom: 24 }}>
154
- <div style={{ background: "#f5f5f5", padding: 16, fontFamily: "monospace", fontSize: 12 }}>
165
+ <div
166
+ style={{
167
+ background: "#f5f5f5",
168
+ padding: 16,
169
+ fontFamily: "monospace",
170
+ fontSize: 12,
171
+ }}
172
+ >
155
173
  <div>
156
174
  <Text strong>Form Type:</Text>{" "}
157
- <Text code>StrictFormValues = {`{ username: string; email: string; age: number }`}</Text>
175
+ <Text code>
176
+ StrictFormValues ={" "}
177
+ {`{ username: string; email: string; age: number }`}
178
+ </Text>
158
179
  </div>
159
180
  <div style={{ marginTop: 8 }}>
160
181
  <Text strong>New Types:</Text>
161
182
  </div>
162
183
  <ul style={{ marginLeft: 20, marginTop: 4 }}>
163
184
  <li>
164
- <Text code>{`name: keyof T | (string & {})`}</Text> - Supports dynamic fields
185
+ <Text code>{`name: keyof T | (string & {})`}</Text> - Supports
186
+ dynamic fields
165
187
  </li>
166
188
  <li>
167
- <Text code>{`getFieldErrors: (...) => Array<{ name: string; errors: FormFieldError[] }>`}</Text>
189
+ <Text
190
+ code
191
+ >{`getFieldErrors: (...) => Array<{ name: string; errors: FormFieldError[] }>`}</Text>
168
192
  </li>
169
193
  <li>
170
194
  <Text code>{`submitAsync: (...) => Promise<void>`}</Text>
@@ -227,7 +251,7 @@ export default function TestCase8_TypeSafetyImprovements() {
227
251
  <Text strong>6. Submit Async:</Text>
228
252
  <div style={{ marginTop: 8 }}>
229
253
  <Button onClick={handleSubmitAsync} type="dashed" block>
230
- Test submitAsync (Promise{'<'}void{'>'})
254
+ Test submitAsync (Promise{"<"}void{">"})
231
255
  </Button>
232
256
  </div>
233
257
  </div>
@@ -287,7 +311,11 @@ export default function TestCase8_TypeSafetyImprovements() {
287
311
  label="Username"
288
312
  rules={[{ required: true, message: "Username is required" }]}
289
313
  >
290
- <input type="text" placeholder="Username" style={{ padding: 8, width: "100%" }} />
314
+ <input
315
+ type="text"
316
+ placeholder="Username"
317
+ style={{ padding: 8, width: "100%" }}
318
+ />
291
319
  </Form.Item>
292
320
 
293
321
  <Form.Item
@@ -298,7 +326,11 @@ export default function TestCase8_TypeSafetyImprovements() {
298
326
  { isEmail: true, message: "Invalid email format" },
299
327
  ]}
300
328
  >
301
- <input type="email" placeholder="Email" style={{ padding: 8, width: "100%" }} />
329
+ <input
330
+ type="email"
331
+ placeholder="Email"
332
+ style={{ padding: 8, width: "100%" }}
333
+ />
302
334
  </Form.Item>
303
335
 
304
336
  <Form.Item
@@ -309,7 +341,11 @@ export default function TestCase8_TypeSafetyImprovements() {
309
341
  { min: 18, message: "Must be at least 18" },
310
342
  ]}
311
343
  >
312
- <input type="number" placeholder="Age" style={{ padding: 8, width: "100%" }} />
344
+ <input
345
+ type="number"
346
+ placeholder="Age"
347
+ style={{ padding: 8, width: "100%" }}
348
+ />
313
349
  </Form.Item>
314
350
 
315
351
  <Button type="primary" htmlType="submit">
@@ -320,7 +356,10 @@ export default function TestCase8_TypeSafetyImprovements() {
320
356
 
321
357
  {/* Field Errors Table */}
322
358
  {fieldErrors.length > 0 && (
323
- <Card title="🔍 Field Errors (getFieldErrors Result)" style={{ marginTop: 24 }}>
359
+ <Card
360
+ title="🔍 Field Errors (getFieldErrors Result)"
361
+ style={{ marginTop: 24 }}
362
+ >
324
363
  <Table
325
364
  dataSource={fieldErrors}
326
365
  columns={errorColumns}
@@ -331,7 +370,9 @@ export default function TestCase8_TypeSafetyImprovements() {
331
370
  <div style={{ marginTop: 16, padding: 12, background: "#f0f0f0" }}>
332
371
  <Text type="secondary">
333
372
  ✅ Return type:{" "}
334
- <Text code>{`Array<{ name: string; errors: FormFieldError[] }>`}</Text>
373
+ <Text
374
+ code
375
+ >{`Array<{ name: string; errors: FormFieldError[] }>`}</Text>
335
376
  </Text>
336
377
  </div>
337
378
  </Card>
@@ -340,20 +381,36 @@ export default function TestCase8_TypeSafetyImprovements() {
340
381
  <div style={{ marginTop: 24, padding: 16, background: "#f5f5f5" }}>
341
382
  <Title level={5}>Expected Results:</Title>
342
383
  <ul>
343
- <li>✅ Click "Set Typed Fields" → Values set for username, email, age</li>
344
384
  <li>
345
- ✅ Click "Set Dynamic Fields" → Dynamic fields work (not in StrictFormValues type)
385
+ ✅ Click "Set Typed Fields" → Values set for username, email, age
386
+ </li>
387
+ <li>
388
+ ✅ Click "Set Dynamic Fields" → Dynamic fields work (not in
389
+ StrictFormValues type)
390
+ </li>
391
+ <li>
392
+ ✅ Click "Get All Field Values" → Shows all field values including
393
+ dynamic
394
+ </li>
395
+ <li>
396
+ ✅ Click "Get Field Errors" → Returns array with name and errors
397
+ properties
398
+ </li>
399
+ <li>
400
+ ✅ Click "Get Multiple Values" → Returns array of{" "}
401
+ {`{ name, value }`} objects
402
+ </li>
403
+ <li>
404
+ ✅ Click "Test submitAsync" → Returns Promise{"<"}void{">"} (result
405
+ is undefined)
346
406
  </li>
347
- <li>✅ Click "Get All Field Values" → Shows all field values including dynamic</li>
348
407
  <li>
349
- Click "Get Field Errors"Returns array with name and errors properties
408
+ Submit form with empty fields Validation errors appear in
409
+ getFieldErrors
350
410
  </li>
351
- <li>✅ Click "Get Multiple Values" → Returns array of {`{ name, value }`} objects</li>
352
411
  <li>
353
- Click "Test submitAsync"Returns Promise{'<'}void{'>'} (result is undefined)
412
+ Fill form and submit onFinish triggered, no validation errors
354
413
  </li>
355
- <li>✅ Submit form with empty fields → Validation errors appear in getFieldErrors</li>
356
- <li>✅ Fill form and submit → onFinish triggered, no validation errors</li>
357
414
  </ul>
358
415
  </div>
359
416
 
@@ -368,20 +425,22 @@ export default function TestCase8_TypeSafetyImprovements() {
368
425
  <Title level={5}>Type Improvements Summary:</Title>
369
426
  <ul style={{ marginBottom: 0 }}>
370
427
  <li>
371
- <strong>Dynamic Field Names:</strong> <Text code>{`keyof T | (string & {})`}</Text> cho
372
- phép sử dụng động field names không trong type
428
+ <strong>Dynamic Field Names:</strong>{" "}
429
+ <Text code>{`keyof T | (string & {})`}</Text> cho phép sử dụng động
430
+ field names không có trong type
373
431
  </li>
374
432
  <li>
375
- <strong>Proper getFieldErrors Type:</strong> Trả về array thay vì Record, chính xác
376
- với implementation
433
+ <strong>Proper getFieldErrors Type:</strong> Trả về array thay vì
434
+ Record, chính xác với implementation
377
435
  </li>
378
436
  <li>
379
- <strong>submitAsync Type:</strong> <Text code>{`Promise<void>`}</Text> thay vì{" "}
437
+ <strong>submitAsync Type:</strong>{" "}
438
+ <Text code>{`Promise<void>`}</Text> thay vì{" "}
380
439
  <Text code>{`Promise<any>`}</Text> for better type safety
381
440
  </li>
382
441
  <li>
383
- <strong>Consistency:</strong> Tất cả methods giờ support cùng type pattern cho field
384
- names
442
+ <strong>Consistency:</strong> Tất cả methods giờ support cùng type
443
+ pattern cho field names
385
444
  </li>
386
445
  </ul>
387
446
  </div>
@@ -124,8 +124,8 @@ export default function FormInstanceTestSuite() {
124
124
  </li>
125
125
  <li>
126
126
  <Text strong>Type Improvements:</Text> Dynamic field names{" "}
127
- <Text code>{`keyof T | (string & {})`}</Text>, proper getFieldErrors return
128
- type, submitAsync <Text code>Promise&lt;void&gt;</Text>
127
+ <Text code>{`keyof T | (string & {})`}</Text>, proper getFieldErrors
128
+ return type, submitAsync <Text code>Promise&lt;void&gt;</Text>
129
129
  </li>
130
130
  </ul>
131
131
  </div>
@@ -1,4 +1,5 @@
1
1
  import type { SUBMIT_STATE } from "../constants/form";
2
+ import { OnChangeOptions } from "../hooks/useFormItemControl";
2
3
  import { SetFieldValueOptions } from "../providers/Form";
3
4
  import type { GetConstantType } from "./util";
4
5
 
@@ -44,7 +45,7 @@ export interface PublicFormInstance<T = any> {
44
45
  value: any,
45
46
  options?: SetFieldValueOptions,
46
47
  ) => void;
47
- setFieldValues: (values: Partial<T>, options?: SetFieldValueOptions) => void;
48
+ setFieldValues: (values: Partial<T>, options?: OnChangeOptions) => void;
48
49
  getFieldValue: (name: keyof T | (string & {})) => any;
49
50
  getFieldValues: (
50
51
  names?: Array<keyof T | (string & {})>,