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 +34 -0
- package/dist/components/Form/FormItem.js +1 -3
- package/dist/providers/Form.js +13 -13
- package/dist/test/SelfTestGetForm.d.ts +3 -0
- package/dist/test/SelfTestGetForm.js +19 -0
- package/dist/test/testFormInstance/TestCase8_TypeSafetyImprovements.js +8 -3
- package/dist/types/public.d.ts +2 -1
- package/package.json +1 -1
- package/src/App.tsx +4 -4
- package/src/components/Form/FormItem.tsx +4 -4
- package/src/hooks/useFormItemControl.ts +1 -1
- package/src/providers/Form.tsx +16 -14
- package/src/test/SelfTestGetForm.tsx +30 -0
- package/src/test/testFormInstance/TestCase7_SetValueInEffect.tsx +28 -22
- package/src/test/testFormInstance/TestCase8_TypeSafetyImprovements.tsx +93 -34
- package/src/test/testFormInstance/index.tsx +2 -2
- package/src/types/public.ts +2 -1
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,
|
|
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,
|
package/dist/providers/Form.js
CHANGED
|
@@ -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 (
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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,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: {
|
|
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
|
|
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",
|
package/dist/types/public.d.ts
CHANGED
|
@@ -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?:
|
|
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
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
|
|
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,
|
|
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
|
-
|
|
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
|
});
|
package/src/providers/Form.tsx
CHANGED
|
@@ -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 (
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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(
|
|
863
|
-
|
|
864
|
-
|
|
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
|
|
189
|
-
được gọi từ async callbacks (setTimeout). Timeout được tạo
|
|
190
|
-
mount, và sau khi trigger, setValue phải hoạt động
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
148
|
-
|
|
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
|
|
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>
|
|
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
|
|
185
|
+
<Text code>{`name: keyof T | (string & {})`}</Text> - Supports
|
|
186
|
+
dynamic fields
|
|
165
187
|
</li>
|
|
166
188
|
<li>
|
|
167
|
-
<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{
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
✅
|
|
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
|
-
✅
|
|
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>
|
|
372
|
-
|
|
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ì
|
|
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>
|
|
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
|
|
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
|
|
128
|
-
type, submitAsync <Text code>Promise<void></Text>
|
|
127
|
+
<Text code>{`keyof T | (string & {})`}</Text>, proper getFieldErrors
|
|
128
|
+
return type, submitAsync <Text code>Promise<void></Text>
|
|
129
129
|
</li>
|
|
130
130
|
</ul>
|
|
131
131
|
</div>
|
package/src/types/public.ts
CHANGED
|
@@ -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?:
|
|
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 & {})>,
|