react-form-manage 1.0.8-beta.6 → 1.0.8-beta.7

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
@@ -4,10 +4,14 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [1.0.8-beta.3] - 2026-01-22
6
6
 
7
- - Add `UseFormItemStateWatchReturn` type export for `useFormItemStateWatch` hook
8
- - Explicit return typing with `FormFieldError[]` for better TypeScript support
7
+ ## [1.0.8-beta.7] - 2026-01-24
8
+ - Add control flag for FormItem to support rendering MUI uncontrolled components when initial value is undefined (control on init).
9
+ - Fix: `onReset` did not restore listener state to init.
10
+ - Fix: reset did not return Form submit state to `idle`.
11
+ - Add `hidden` prop to allow hiding components while still assigning a value.
9
12
 
10
- ## [1.0.8-beta.2] - 2026-01-22
13
+ Docs: Update usage notes for `FormItem` control-on-init flag and `hidden` prop.
14
+ - Add `UseFormItemStateWatchReturn` type export for `useFormItemStateWatch` hook
11
15
 
12
16
  - Add isTouched field to FormItem for tracking user interaction state
13
17
 
@@ -9,5 +9,7 @@ export interface FormItemProps {
9
9
  rules?: ValidationRule[];
10
10
  valuePropName?: string;
11
11
  getValueFromEvent?: (...args: any[]) => any;
12
+ controlAfterInit?: boolean;
13
+ hidden?: boolean;
12
14
  }
13
- export default function FormItem({ children, name, formName, initialValue, formItemId: externalFormItemId, rules, valuePropName, getValueFromEvent, }: FormItemProps): ReactElement<any, string | import("react").JSXElementConstructor<any>>;
15
+ export default function FormItem({ children, name, formName, initialValue, formItemId: externalFormItemId, rules, valuePropName, getValueFromEvent, controlAfterInit, hidden, }: FormItemProps): import("react/jsx-runtime").JSX.Element;
@@ -1,10 +1,11 @@
1
- import { cloneElement, useRef, useState } from "react";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cloneElement, Fragment, useRef, useState } from "react";
2
3
  import { v4 } from "uuid";
3
4
  import useFormItemControl from "../../hooks/useFormItemControl";
4
- function FormItem({ children, name, formName, initialValue, formItemId: externalFormItemId, rules, valuePropName = "value", getValueFromEvent }) {
5
+ function FormItem({ children, name, formName, initialValue, formItemId: externalFormItemId, rules, valuePropName = "value", getValueFromEvent, controlAfterInit = false, hidden }) {
5
6
  const elRef = useRef(null);
6
7
  const [formItemId] = useState(externalFormItemId != null ? externalFormItemId : v4());
7
- const { value, onChange, errors, state, onFocus, isDirty, submitState, isTouched } = useFormItemControl({
8
+ const { value, onChange, errors, state, onFocus, isDirty, submitState, isTouched, isInitied } = useFormItemControl({
8
9
  formName,
9
10
  name,
10
11
  initialValue,
@@ -12,7 +13,7 @@ function FormItem({ children, name, formName, initialValue, formItemId: external
12
13
  rules,
13
14
  elementRef: elRef
14
15
  });
15
- return cloneElement(children, {
16
+ return _jsx(Fragment, { children: !hidden && children ? cloneElement(children, {
16
17
  name,
17
18
  // ref: inputRef,
18
19
  [valuePropName]: value,
@@ -41,7 +42,7 @@ function FormItem({ children, name, formName, initialValue, formItemId: external
41
42
  ref: elRef,
42
43
  submitState,
43
44
  isTouched
44
- });
45
+ }) : null }, `control-after-init-${Boolean(controlAfterInit && isInitied) ? "1" : "0"}-${formItemId}`);
45
46
  }
46
47
  export {
47
48
  FormItem as default
@@ -20,6 +20,7 @@ export interface UseFormItemControlReturn {
20
20
  isDirty?: boolean;
21
21
  isTouched?: boolean;
22
22
  submitState?: SubmitState;
23
+ isInitied?: boolean;
23
24
  }
24
25
  export default function useFormItemControl<T = any>({ formName, form, name, initialValue, formItemId, rules, elementRef, }: UseFormItemControlProps): UseFormItemControlReturn;
25
26
  export {};
@@ -38,7 +38,7 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
38
38
  }));
39
39
  const onInitData = (value2) => {
40
40
  setInitData(formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName), name, value2);
41
- onChange(value2, { notTriggerDirty: true });
41
+ onChange(value2, { notTriggerDirty: true, initiedData: true });
42
42
  };
43
43
  const onFocus = () => {
44
44
  setListener({
@@ -60,10 +60,21 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
60
60
  isTouched: listener == null ? void 0 : listener.isTouched
61
61
  });
62
62
  }
63
+ if ((options == null ? void 0 : options.initiedData) === true) {
64
+ setListener({
65
+ formItemId,
66
+ isInitied: true
67
+ });
68
+ }
63
69
  setData(formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName), name, value2);
64
70
  };
65
71
  const onReset = (value2) => {
66
- onChange(isNil(value2) ? getInitData(formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName), name) : value2);
72
+ setListener({
73
+ formItemId,
74
+ isDirty: false,
75
+ isTouched: false
76
+ });
77
+ onChange(isNil(value2) ? getInitData(formName || (form == null ? void 0 : form.formName) || (contextForm == null ? void 0 : contextForm.formName), name) : value2, { notTriggerDirty: true, initiedData: true });
67
78
  };
68
79
  const internalRules = useMemo(() => {
69
80
  return rules || [];
@@ -262,7 +273,10 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
262
273
  onInitData(initialValue);
263
274
  }
264
275
  } else {
265
- onChange(internalInitValue, { notTriggerDirty: true });
276
+ onChange(internalInitValue, {
277
+ notTriggerDirty: true,
278
+ initiedData: true
279
+ });
266
280
  }
267
281
  }
268
282
  return;
@@ -320,7 +334,8 @@ function useFormItemControl({ formName, form, name, initialValue, formItemId, ru
320
334
  onFocus,
321
335
  isDirty: listener == null ? void 0 : listener.isDirty,
322
336
  isTouched: listener == null ? void 0 : listener.isTouched,
323
- submitState
337
+ submitState,
338
+ isInitied: listener == null ? void 0 : listener.isInitied
324
339
  };
325
340
  }
326
341
  export {
@@ -1,6 +1,6 @@
1
1
  import type { ComponentType, FormHTMLAttributes, ReactNode } from "react";
2
2
  import type { PublicFormInstance, UseFormItemStateWatchReturn } from "../types/public";
3
- export type { FormFieldError, SubmitState, ValidationRule, UseFormItemStateWatchReturn, } from "../types/public";
3
+ export type { FormFieldError, SubmitState, UseFormItemStateWatchReturn, ValidationRule, } from "../types/public";
4
4
  export declare const FormContext: import("react").Context<any>;
5
5
  export interface FormProps<T = any> extends Omit<FormHTMLAttributes<HTMLFormElement>, "onSubmit"> {
6
6
  children: ReactNode;
@@ -5,6 +5,7 @@ import { createContext, useContext, useEffect, useState } from "react";
5
5
  import { flushSync } from "react-dom";
6
6
  import { useShallow } from "zustand/react/shallow";
7
7
  import FormCleanUp from "../components/Form/FormCleanUp";
8
+ import { SUBMIT_STATE } from "../constants/form";
8
9
  import { useFormListeners, useFormStore } from "../stores/formStore";
9
10
  import { getAllNoneObjStringPath } from "../utils/obj.util";
10
11
  const FormContext = createContext(null);
@@ -167,7 +168,8 @@ function Form({ children, formName, initialValues, onFinish, onReject, onFinally
167
168
  reset();
168
169
  flushSync(setFormState({
169
170
  formName,
170
- isInitied: false
171
+ isInitied: false,
172
+ submitState: SUBMIT_STATE.IDLE
171
173
  }));
172
174
  const totalListenerFields = getListeners();
173
175
  if (Array.isArray(resetOptions)) {
@@ -200,7 +200,7 @@ const useFormListeners = create((storeSet, storeGet) => ({
200
200
  getListeners() {
201
201
  return storeGet().listeners;
202
202
  },
203
- setListener({ formName, name, onChange, onReset, isTouched, isDirty, formItemId, internalErrors, onFocus, emitFocus }) {
203
+ setListener({ formName, name, onChange, onReset, isTouched, isDirty, formItemId, internalErrors, onFocus, emitFocus, isInitied }) {
204
204
  return storeSet(produce((state) => {
205
205
  const storeListeners = state.listeners;
206
206
  const findListenerIndex = state.listeners.findIndex((l) => l.formItemId === formItemId);
@@ -232,17 +232,21 @@ const useFormListeners = create((storeSet, storeGet) => ({
232
232
  if (!isNil(emitFocus)) {
233
233
  storeListeners[findListenerIndex].emitFocus = emitFocus;
234
234
  }
235
+ if (!isNil(isInitied)) {
236
+ storeListeners[findListenerIndex].isInitied = isInitied;
237
+ }
235
238
  return;
236
239
  }
237
240
  storeListeners.push({
238
241
  name,
239
242
  formName,
240
- isTouched,
241
- isDirty,
243
+ isTouched: Boolean(isTouched),
244
+ isDirty: Boolean(isDirty),
242
245
  formItemId,
243
246
  internalErrors,
244
247
  onChange,
245
- onReset
248
+ onReset,
249
+ isInitied: Boolean(isInitied)
246
250
  });
247
251
  }));
248
252
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-form-manage",
3
- "version": "1.0.8-beta.6",
3
+ "version": "1.0.8-beta.7",
4
4
  "description": "Lightweight React form management with list and listener support.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -45,6 +45,7 @@
45
45
  "eslint": "^9.14.0",
46
46
  "eslint-plugin-react-hooks": "^5.1.0",
47
47
  "eslint-plugin-react-refresh": "^0.4.14",
48
+ "framer-motion": "^12.29.0",
48
49
  "globals": "^15.12.0",
49
50
  "react": "^19.0.0",
50
51
  "react-dom": "^19.0.0",
package/src/App.tsx CHANGED
@@ -1,13 +1,19 @@
1
+ import { Checkbox } from "@mui/material";
1
2
  import { Button, Input } from "antd";
3
+ import { motion } from "framer-motion";
2
4
  import { useEffect } from "react";
3
5
  import FormItem from "./components/Form/FormItem";
4
6
  import FormList from "./components/Form/FormList";
5
7
  import InputWrapper from "./components/Form/InputWrapper";
6
8
  import Form, { useForm } from "./providers/Form";
7
9
 
10
+ import { Form as AntdForm } from "antd";
11
+
8
12
  const App = () => {
9
13
  const [form] = useForm("form1");
10
14
 
15
+ const watchCheckBox = Form.useWatch("checkControlledAfterInit", "form1");
16
+
11
17
  useEffect(() => {
12
18
  if (form) {
13
19
  // setTimeout(() => {
@@ -17,6 +23,20 @@ const App = () => {
17
23
  }, [form]);
18
24
  return (
19
25
  <div>
26
+ <AntdForm>
27
+ <motion.div
28
+ initial={{ opacity: 0 }}
29
+ animate={{ opacity: 1 }}
30
+ exit={{ opacity: 0 }}
31
+ transition={{ duration: 1 }}
32
+ >
33
+ <AntdForm.Item name={"234"} initialValue={"23432"} label="Antd Input">
34
+ <Input />
35
+ </AntdForm.Item>
36
+ </motion.div>
37
+ </AntdForm>
38
+
39
+ {/* Hidden Test */}
20
40
  <Form
21
41
  initialValues={{
22
42
  TestData: "",
@@ -27,6 +47,7 @@ const App = () => {
27
47
  console.log(values);
28
48
  }}
29
49
  formName={"form1"}
50
+ // hidden
30
51
  >
31
52
  <FormItem
32
53
  name={"username"}
@@ -36,12 +57,15 @@ const App = () => {
36
57
  message: "Test",
37
58
  },
38
59
  ]}
60
+ initialValue={"283746"}
61
+ // hidden
39
62
  >
40
63
  <InputWrapper>
41
64
  <Input />
42
65
  </InputWrapper>
43
66
  </FormItem>
44
67
 
68
+ {/* Numberic test */}
45
69
  <FormItem
46
70
  name={"numericCode"}
47
71
  rules={[
@@ -56,6 +80,20 @@ const App = () => {
56
80
  <Input placeholder="Mã chỉ gồm số" style={{ width: 200 }} />
57
81
  </InputWrapper>
58
82
  </FormItem>
83
+
84
+ {/* Motion Test */}
85
+ <motion.div
86
+ initial={{ opacity: 0 }}
87
+ animate={{ opacity: 1 }}
88
+ exit={{ opacity: 0 }}
89
+ transition={{ duration: 1 }}
90
+ >
91
+ <FormItem name="motionTest" controlAfterInit initialValue={"1234134"}>
92
+ <InputWrapper>
93
+ <Input placeholder="Motion Test" style={{ width: 200 }} />
94
+ </InputWrapper>
95
+ </FormItem>
96
+ </motion.div>
59
97
  <FormList
60
98
  initialValues={[
61
99
  {
@@ -101,12 +139,32 @@ const App = () => {
101
139
  </div>
102
140
  )}
103
141
  </FormList>
142
+ <FormItem
143
+ valuePropName="checked"
144
+ getValueFromEvent={(_, checked) => checked}
145
+ name="checkControlledAfterInit"
146
+ controlAfterInit={true}
147
+ initialValue={true}
148
+ >
149
+ <Checkbox />
150
+ </FormItem>
151
+ <Button
152
+ onClick={() => {
153
+ const current = form?.getFieldValue("checkControlledAfterInit");
154
+ console.log("Toggle controlled after init: ", current);
155
+ form?.setFieldValue("checkControlledAfterInit", !current);
156
+ }}
157
+ >
158
+ Toggle
159
+ </Button>
104
160
  <Button htmlType="submit">Submit</Button>
105
161
  <Button
106
162
  onClick={() => {
107
163
  form?.resetFields?.();
108
164
  }}
109
- ></Button>
165
+ >
166
+ Reset
167
+ </Button>
110
168
  </Form>
111
169
  </div>
112
170
  );
@@ -1,5 +1,5 @@
1
1
  import type { ReactElement } from "react";
2
- import { cloneElement, useRef, useState } from "react";
2
+ import { cloneElement, Fragment, useRef, useState } from "react";
3
3
  import { v4 } from "uuid";
4
4
  import useFormItemControl from "../../hooks/useFormItemControl";
5
5
  import type { ValidationRule } from "../../types/public";
@@ -13,6 +13,8 @@ export interface FormItemProps {
13
13
  rules?: ValidationRule[];
14
14
  valuePropName?: string;
15
15
  getValueFromEvent?: (...args: any[]) => any;
16
+ controlAfterInit?: boolean;
17
+ hidden?: boolean;
16
18
  }
17
19
 
18
20
  export default function FormItem({
@@ -24,6 +26,8 @@ export default function FormItem({
24
26
  rules,
25
27
  valuePropName = "value",
26
28
  getValueFromEvent,
29
+ controlAfterInit = false,
30
+ hidden,
27
31
  }: FormItemProps) {
28
32
  const elRef = useRef<any>(null);
29
33
 
@@ -37,6 +41,7 @@ export default function FormItem({
37
41
  isDirty,
38
42
  submitState,
39
43
  isTouched,
44
+ isInitied,
40
45
  } = useFormItemControl({
41
46
  formName,
42
47
  name,
@@ -51,35 +56,55 @@ export default function FormItem({
51
56
  // console.log({ value });
52
57
  // }, [value]);
53
58
 
54
- return cloneElement(children, {
55
- name,
56
- // ref: inputRef,
57
- [valuePropName]: value,
58
- onChange: (...args: any[]) => {
59
- let val = args[0];
60
- if (getValueFromEvent && typeof getValueFromEvent === "function") {
61
- val = getValueFromEvent(...args);
62
- } else {
63
- const e = args[0];
64
- if (e && e.target) {
65
- val = e.target.value;
66
- }
67
- }
68
- onChange(val);
69
- },
70
- // onFocus: () => {
71
- // setIsTouched(true);
72
- // },
73
- // isTouched: isTouched,
74
- isDirty: isDirty,
75
- // errors: errors,
76
- // formState,
59
+ // useEffect(() => {
60
+ // console.log("isInitied changed: ", {
61
+ // isInitied,
62
+ // name,
63
+ // value,
64
+ // controlAfterInit,
65
+ // });
66
+ // }, [isInitied]);
77
67
 
78
- errors,
79
- onFocus,
80
- validateState: state,
81
- ref: elRef,
82
- submitState,
83
- isTouched: isTouched,
84
- } as any);
68
+ return (
69
+ <Fragment
70
+ key={`control-after-init-${Boolean(controlAfterInit && isInitied) ? "1" : "0"}-${formItemId}`}
71
+ >
72
+ {!hidden && children
73
+ ? cloneElement(children, {
74
+ name,
75
+ // ref: inputRef,
76
+ [valuePropName]: value,
77
+ onChange: (...args: any[]) => {
78
+ let val = args[0];
79
+ if (
80
+ getValueFromEvent &&
81
+ typeof getValueFromEvent === "function"
82
+ ) {
83
+ val = getValueFromEvent(...args);
84
+ } else {
85
+ const e = args[0];
86
+ if (e && e.target) {
87
+ val = e.target.value;
88
+ }
89
+ }
90
+ onChange(val);
91
+ },
92
+ // onFocus: () => {
93
+ // setIsTouched(true);
94
+ // },
95
+ // isTouched: isTouched,
96
+ isDirty: isDirty,
97
+ // errors: errors,
98
+ // formState,
99
+
100
+ errors,
101
+ onFocus,
102
+ validateState: state,
103
+ ref: elRef,
104
+ submitState,
105
+ isTouched: isTouched,
106
+ } as any)
107
+ : null}
108
+ </Fragment>
109
+ );
85
110
  }
@@ -13,6 +13,11 @@ const InputWrapper = ({
13
13
  errors = [],
14
14
  ...props
15
15
  }: InputWrapperProps) => {
16
+ // useEffect(() => {
17
+ // console.log("InputWrapper submitState changed: ", {
18
+ // submitState: props.submitState,
19
+ // });
20
+ // }, [props.submitState]);
16
21
  return (
17
22
  <div>
18
23
  {cloneElement(children, props)}
@@ -52,6 +52,7 @@ export interface UseFormItemControlReturn {
52
52
  isDirty?: boolean;
53
53
  isTouched?: boolean;
54
54
  submitState?: SubmitState;
55
+ isInitied?: boolean;
55
56
  }
56
57
 
57
58
  const VALID_PREMITIVE_TYPE = ["string", "number", "undefined"];
@@ -154,7 +155,7 @@ export default function useFormItemControl<T = any>({
154
155
  name,
155
156
  value,
156
157
  );
157
- onChange(value, { notTriggerDirty: true });
158
+ onChange(value, { notTriggerDirty: true, initiedData: true });
158
159
  };
159
160
 
160
161
  const onFocus = () => {
@@ -171,7 +172,13 @@ export default function useFormItemControl<T = any>({
171
172
  }
172
173
  };
173
174
 
174
- const onChange = (value: T, options?: any) => {
175
+ const onChange = (
176
+ value: T,
177
+ options?: {
178
+ notTriggerDirty?: boolean;
179
+ initiedData?: boolean;
180
+ },
181
+ ) => {
175
182
  if (options?.notTriggerDirty !== true) {
176
183
  setListener({
177
184
  formItemId,
@@ -179,6 +186,12 @@ export default function useFormItemControl<T = any>({
179
186
  isTouched: listener?.isTouched,
180
187
  });
181
188
  }
189
+ if (options?.initiedData === true) {
190
+ setListener({
191
+ formItemId,
192
+ isInitied: true,
193
+ });
194
+ }
182
195
  setData(formName || form?.formName || contextForm?.formName, name, value);
183
196
  };
184
197
 
@@ -188,10 +201,16 @@ export default function useFormItemControl<T = any>({
188
201
  value,
189
202
  getInitData(formName || form?.formName || contextForm?.formName, name),
190
203
  );
204
+ setListener({
205
+ formItemId,
206
+ isDirty: false,
207
+ isTouched: false,
208
+ });
191
209
  onChange(
192
210
  isNil(value)
193
211
  ? getInitData(formName || form?.formName || contextForm?.formName, name)
194
212
  : value,
213
+ { notTriggerDirty: true, initiedData: true },
195
214
  );
196
215
  };
197
216
 
@@ -522,7 +541,10 @@ export default function useFormItemControl<T = any>({
522
541
  onInitData(initialValue);
523
542
  }
524
543
  } else {
525
- onChange(internalInitValue, { notTriggerDirty: true });
544
+ onChange(internalInitValue, {
545
+ notTriggerDirty: true,
546
+ initiedData: true,
547
+ });
526
548
  }
527
549
  }
528
550
  return;
@@ -599,5 +621,6 @@ export default function useFormItemControl<T = any>({
599
621
  isDirty: listener?.isDirty,
600
622
  isTouched: listener?.isTouched,
601
623
  submitState,
624
+ isInitied: listener?.isInitied,
602
625
  };
603
626
  }
@@ -5,14 +5,18 @@ import { createContext, useContext, useEffect, useState } from "react";
5
5
  import { flushSync } from "react-dom";
6
6
  import { useShallow } from "zustand/react/shallow"; // Import useShallow
7
7
  import FormCleanUp from "../components/Form/FormCleanUp";
8
+ import { SUBMIT_STATE } from "../constants/form";
8
9
  import { useFormListeners, useFormStore } from "../stores/formStore";
9
- import type { PublicFormInstance, UseFormItemStateWatchReturn } from "../types/public";
10
+ import type {
11
+ PublicFormInstance,
12
+ UseFormItemStateWatchReturn,
13
+ } from "../types/public";
10
14
  import { getAllNoneObjStringPath } from "../utils/obj.util";
11
15
  export type {
12
16
  FormFieldError,
13
17
  SubmitState,
14
- ValidationRule,
15
18
  UseFormItemStateWatchReturn,
19
+ ValidationRule,
16
20
  } from "../types/public";
17
21
 
18
22
  export const FormContext = createContext(null);
@@ -276,6 +280,7 @@ export default function Form<T = any>({
276
280
  setFormState({
277
281
  formName,
278
282
  isInitied: false,
283
+ submitState: SUBMIT_STATE.IDLE,
279
284
  }),
280
285
  );
281
286
  const totalListenerFields = getListeners();
@@ -375,6 +375,7 @@ export const useFormListeners = create<any>((storeSet: any, storeGet: any) => ({
375
375
  internalErrors,
376
376
  onFocus,
377
377
  emitFocus,
378
+ isInitied,
378
379
  }) {
379
380
  return storeSet(
380
381
  produce<any>((state: any) => {
@@ -412,17 +413,22 @@ export const useFormListeners = create<any>((storeSet: any, storeGet: any) => ({
412
413
  storeListeners[findListenerIndex].emitFocus = emitFocus;
413
414
  }
414
415
 
416
+ if (!isNil(isInitied)) {
417
+ storeListeners[findListenerIndex].isInitied = isInitied;
418
+ }
419
+
415
420
  return;
416
421
  }
417
422
  storeListeners.push({
418
423
  name,
419
424
  formName,
420
- isTouched,
421
- isDirty,
425
+ isTouched: Boolean(isTouched),
426
+ isDirty: Boolean(isDirty),
422
427
  formItemId,
423
428
  internalErrors,
424
429
  onChange,
425
430
  onReset,
431
+ isInitied: Boolean(isInitied),
426
432
  });
427
433
  }),
428
434
  );