remix-validated-form 4.6.11 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,12 +7,12 @@
7
7
  CLI Cleaning output folder
8
8
  ESM Build start
9
9
  CJS Build start
10
- ESM dist/index.esm.js 56.25 KB
11
- ESM dist/index.esm.js.map 121.08 KB
12
- ESM ⚡️ Build success in 27ms
13
- CJS dist/index.cjs.js 58.87 KB
14
- CJS dist/index.cjs.js.map 121.22 KB
15
- CJS ⚡️ Build success in 26ms
10
+ CJS dist/index.cjs.js 63.04 KB
11
+ CJS dist/index.cjs.js.map 130.86 KB
12
+ CJS ⚡️ Build success in 25ms
13
+ ESM dist/index.esm.js 60.30 KB
14
+ ESM dist/index.esm.js.map 130.76 KB
15
+ ESM ⚡️ Build success in 25ms
16
16
  DTS Build start
17
- DTS ⚡️ Build success in 1588ms
18
- DTS dist/index.d.ts 11.67 KB
17
+ DTS ⚡️ Build success in 1486ms
18
+ DTS dist/index.d.ts 12.80 KB
package/README.md CHANGED
@@ -2,25 +2,27 @@
2
2
 
3
3
  A form library built for [Remix](https://remix.run) to make validation easy.
4
4
 
5
+ ## Features
6
+
5
7
  - Client-side, field-by-field and form-level validation
6
8
  - Re-use validation on the server
7
9
  - Set default values for the entire form in one place
8
10
  - Supports nested objects and arrays
9
- - Easily detect if a specific form is being sumitted
11
+ - Easily detect if a specific form is being submitted
10
12
  - Validation library agnostic
11
13
  - Can work without JS
12
14
 
13
- # Docs
15
+ ## Docs
14
16
 
15
17
  The docs are located a [remix-validated-form.io](https://www.remix-validated-form.io).
16
18
 
17
- # Demo
19
+ ## Demo
18
20
 
19
21
  https://user-images.githubusercontent.com/2811287/145734901-700a5085-a10b-4d89-88e1-5de9142b1e85.mov
20
22
 
21
23
  To run `sample-app`:
22
24
 
23
- ```
25
+ ```bash
24
26
  git clone https://github.com/airjp73/remix-validated-form
25
27
  cd ./remix-validated-form
26
28
  yarn install
@@ -28,21 +30,20 @@ yarn build
28
30
  yarn sample-app
29
31
  ```
30
32
 
31
- # Getting started
33
+ ## Getting started
32
34
 
33
- ## Install
35
+ ### Install
34
36
 
35
- ### Base package
37
+ #### Base package
36
38
 
37
39
  ```bash
38
40
  npm install remix-validated-form
39
41
  ```
40
42
 
41
- ### Validation library adapter
43
+ #### Validation library adapter
42
44
 
43
45
  There are official adapters available for `zod` and `yup`.
44
- If you're using a different library,
45
- see the [Validation library support](#validation-library-support) section below.
46
+ If you're using a different library, see the [Validation library support](#validation-library-support) section below.
46
47
 
47
48
  - @remix-validated-form/with-zod
48
49
  - @remix-validated-form/with-yup
@@ -53,10 +54,9 @@ npm install @remix-validated-form/with-zod
53
54
 
54
55
  If you're using zod, you might also find `zod-form-data` helpful.
55
56
 
56
- ## Create an input component
57
+ ### Create an input component
57
58
 
58
- In order to display field errors or do field-by-field validation,
59
- it's recommended to incorporate this library into an input component using `useField`.
59
+ In order to display field errors or do field-by-field validation, it's recommended to incorporate this library into an input component using `useField`.
60
60
 
61
61
  ```tsx
62
62
  import { useField } from "remix-validated-form";
@@ -78,7 +78,7 @@ export const MyInput = ({ name, label }: MyInputProps) => {
78
78
  };
79
79
  ```
80
80
 
81
- ## Create a submit button component
81
+ ### Create a submit button component
82
82
 
83
83
  To best take advantage of the per-form submission detection, we can create a submit button component.
84
84
 
@@ -102,9 +102,9 @@ export const MySubmitButton = () => {
102
102
  };
103
103
  ```
104
104
 
105
- ## Use the form!
105
+ ### Using the form
106
106
 
107
- Now that we have our components, making a form is easy!
107
+ Now that we have our components, making a form is easy.
108
108
 
109
109
  ```tsx
110
110
  import { DataFunctionArgs, json, redirect } from "@remix-run/node";
@@ -159,7 +159,7 @@ export default function MyForm() {
159
159
  }
160
160
  ```
161
161
 
162
- ## Nested objects and arrays
162
+ ### Nested objects and arrays
163
163
 
164
164
  You can use nested objects and arrays by using a period (`.`) or brackets (`[]`) for the field names.
165
165
 
@@ -186,14 +186,13 @@ export default function MyForm() {
186
186
  }
187
187
  ```
188
188
 
189
- # Validation Library Support
189
+ ## Validation Library Support
190
190
 
191
- There are official adapters available for `zod` and `yup` ,
192
- but you can easily support whatever library you want by creating your own adapter.
191
+ There are official adapters available for `zod` and `yup` , but you can easily support whatever library you want by creating your own adapter.
193
192
 
194
193
  And if you create an adapter for a library, feel free to make a PR on this repository 😊
195
194
 
196
- ## Creating an adapter
195
+ ### Creating an adapter
197
196
 
198
197
  Any object that conforms to the `Validator` type can be passed into the the `ValidatedForm`'s `validator` prop.
199
198
 
@@ -215,15 +214,13 @@ type Validator<DataType> = {
215
214
  };
216
215
  ```
217
216
 
218
- In order to make an adapter for your validation library of choice,
219
- you can create a function that accepts a schema from the validation library and turns it into a validator.
217
+ In order to make an adapter for your validation library of choice, you can create a function that accepts a schema from the validation library and turns it into a validator.
220
218
 
221
219
  Note the use of `createValidator`.
222
- It takes care of unflattening the data for nested objects and arrays
223
- since the form doesn't know anything about object and arrays and this should be handled by the adapter.
220
+ It takes care of unflattening the data for nested objects and arrays since the form doesn't know anything about object and arrays and this should be handled by the adapter.
224
221
  For more on this you can check the implementations for `withZod` and `withYup`.
225
222
 
226
- The out-of-the-box support for `yup` in this library works like this:
223
+ The out-of-the-box support for `yup` in this library works as the following:
227
224
 
228
225
  ```ts
229
226
  export const withYup = <Schema extends AnyObjectSchema>(
@@ -246,21 +243,21 @@ export const withYup = <Schema extends AnyObjectSchema>(
246
243
  });
247
244
  ```
248
245
 
249
- # Frequenty Asked Questions
246
+ ## Frequenty Asked Questions
250
247
 
251
- ## Why are my fields triggering the native HTML validations before `remix-validated-form` ones?
248
+ ### Why are my fields triggering the native HTML validations before `remix-validated-form` ones?
252
249
 
253
250
  This is happening because you or the library you are using is passing the `required` attribute to the fields.
254
251
  This library doesn't take care of eliminating them and it's up to the user how they want to manage the validation errors.
255
252
  If you wan't to disable all native HTML validations you can add `noValidate` to `<ValidatedForm>`.
256
253
  We recommend this approach since the validation will still work even if JS is disabled.
257
254
 
258
- ## How do we trigger toast messages on success?
255
+ ### How do we trigger toast messages on success?
259
256
 
260
257
  Problem: how do we trigger a toast message on success if the action redirects away from the form route? The Remix solution is to flash a message in the session and pick this up in a loader function, probably in root.tsx
261
258
  See the [Remix](https://remix.run/docs/en/v1/utils/sessions#sessionflashkey-value) documentation for more information.
262
259
 
263
- ## Why is my cancel button triggering form submission?
260
+ ### Why is my cancel button triggering form submission?
264
261
 
265
262
  Problem: the cancel button has an onClick handler to navigate away from the form route but instead it is submitting the form.
266
263
  A button defaults to `type="submit"` in a form which will submit the form by default. If you want to prevent this you can add `type="reset"` or `type="button"` to the cancel button.
package/dist/index.cjs.js CHANGED
@@ -309,7 +309,9 @@ function sparseSplice(array, start, deleteCount, item) {
309
309
  }
310
310
  if (arguments.length === 4)
311
311
  return array.splice(start, deleteCount, item);
312
- return array.splice(start, deleteCount);
312
+ else if (arguments.length === 3)
313
+ return array.splice(start, deleteCount);
314
+ return array.splice(start);
313
315
  }
314
316
  var move = (array, from2, to) => {
315
317
  const [item] = sparseSplice(array, from2, 1);
@@ -318,6 +320,12 @@ var move = (array, from2, to) => {
318
320
  var insert = (array, index, value) => {
319
321
  sparseSplice(array, index, 0, value);
320
322
  };
323
+ var insertEmpty = (array, index) => {
324
+ const tail = sparseSplice(array, index);
325
+ tail.forEach((item, i) => {
326
+ sparseSplice(array, index + i + 1, 0, item);
327
+ });
328
+ };
321
329
  var remove = (array, index) => {
322
330
  sparseSplice(array, index, 1);
323
331
  };
@@ -447,6 +455,29 @@ if (void 0) {
447
455
  expect(array).toEqual([true, void 0, void 0, true]);
448
456
  });
449
457
  });
458
+ describe("insertEmpty", () => {
459
+ it("should insert an empty item at a given index", () => {
460
+ const array = [1, 2, 3];
461
+ insertEmpty(array, 1);
462
+ expect(array).toStrictEqual([1, , 2, 3]);
463
+ expect(array).not.toStrictEqual([1, void 0, 2, 3]);
464
+ });
465
+ it("should work with already sparse arrays", () => {
466
+ const array = [, , 1, , 2, , 3];
467
+ insertEmpty(array, 3);
468
+ expect(array).toStrictEqual([, , 1, , , 2, , 3]);
469
+ expect(array).not.toStrictEqual([
470
+ void 0,
471
+ void 0,
472
+ 1,
473
+ void 0,
474
+ void 0,
475
+ 2,
476
+ void 0,
477
+ 3
478
+ ]);
479
+ });
480
+ });
450
481
  describe("remove", () => {
451
482
  it("should remove an item at a given index", () => {
452
483
  const array = [1, 2, 3];
@@ -628,10 +659,12 @@ var defaultFormState = {
628
659
  reset: () => noOp,
629
660
  syncFormProps: noOp,
630
661
  setFormElement: noOp,
631
- validateField: async () => null,
632
662
  validate: async () => {
633
663
  throw new Error("Validate called before form was initialized.");
634
664
  },
665
+ smartValidate: async () => {
666
+ throw new Error("Validate called before form was initialized.");
667
+ },
635
668
  submit: async () => {
636
669
  throw new Error("Submit called before form was initialized.");
637
670
  },
@@ -714,8 +747,8 @@ var createFormState = (set, get2) => ({
714
747
  state.formElement = formElement;
715
748
  });
716
749
  },
717
- validateField: async (field) => {
718
- var _a, _b, _c;
750
+ validate: async () => {
751
+ var _a;
719
752
  const formElement = get2().formElement;
720
753
  (0, import_tiny_invariant2.default)(
721
754
  formElement,
@@ -724,22 +757,14 @@ var createFormState = (set, get2) => ({
724
757
  const validator = (_a = get2().formProps) == null ? void 0 : _a.validator;
725
758
  (0, import_tiny_invariant2.default)(
726
759
  validator,
727
- "Cannot validator. This is probably a bug in remix-validated-form."
728
- );
729
- await ((_c = (_b = get2().controlledFields).awaitValueUpdate) == null ? void 0 : _c.call(_b, field));
730
- const { error } = await validator.validateField(
731
- new FormData(formElement),
732
- field
760
+ "Cannot find validator. This is probably a bug in remix-validated-form."
733
761
  );
734
- if (error) {
735
- get2().setFieldError(field, error);
736
- return error;
737
- } else {
738
- get2().clearFieldError(field);
739
- return null;
740
- }
762
+ const result = await validator.validate(new FormData(formElement));
763
+ if (result.error)
764
+ get2().setFieldErrors(result.error.fieldErrors);
765
+ return result;
741
766
  },
742
- validate: async () => {
767
+ smartValidate: async ({ alwaysIncludeErrorsFromFields = [] } = {}) => {
743
768
  var _a;
744
769
  const formElement = get2().formElement;
745
770
  (0, import_tiny_invariant2.default)(
@@ -749,12 +774,75 @@ var createFormState = (set, get2) => ({
749
774
  const validator = (_a = get2().formProps) == null ? void 0 : _a.validator;
750
775
  (0, import_tiny_invariant2.default)(
751
776
  validator,
752
- "Cannot validator. This is probably a bug in remix-validated-form."
777
+ "Cannot find validator. This is probably a bug in remix-validated-form."
753
778
  );
754
- const result = await validator.validate(new FormData(formElement));
755
- if (result.error)
756
- get2().setFieldErrors(result.error.fieldErrors);
757
- return result;
779
+ await Promise.all(
780
+ alwaysIncludeErrorsFromFields.map(
781
+ (field) => {
782
+ var _a2, _b;
783
+ return (_b = (_a2 = get2().controlledFields).awaitValueUpdate) == null ? void 0 : _b.call(_a2, field);
784
+ }
785
+ )
786
+ );
787
+ const validationResult = await validator.validate(
788
+ new FormData(formElement)
789
+ );
790
+ if (!validationResult.error) {
791
+ const hadErrors = Object.keys(get2().fieldErrors).length > 0;
792
+ if (hadErrors)
793
+ get2().setFieldErrors({});
794
+ return validationResult;
795
+ }
796
+ const {
797
+ error: { fieldErrors }
798
+ } = validationResult;
799
+ const errorFields = /* @__PURE__ */ new Set();
800
+ const incomingErrors = /* @__PURE__ */ new Set();
801
+ const prevErrors = /* @__PURE__ */ new Set();
802
+ Object.keys(fieldErrors).forEach((field) => {
803
+ errorFields.add(field);
804
+ incomingErrors.add(field);
805
+ });
806
+ Object.keys(get2().fieldErrors).forEach((field) => {
807
+ errorFields.add(field);
808
+ prevErrors.add(field);
809
+ });
810
+ const fieldsToUpdate = /* @__PURE__ */ new Set();
811
+ const fieldsToDelete = /* @__PURE__ */ new Set();
812
+ errorFields.forEach((field) => {
813
+ if (!incomingErrors.has(field)) {
814
+ fieldsToDelete.add(field);
815
+ return;
816
+ }
817
+ if (prevErrors.has(field) && incomingErrors.has(field)) {
818
+ if (fieldErrors[field] !== get2().fieldErrors[field])
819
+ fieldsToUpdate.add(field);
820
+ return;
821
+ }
822
+ if (alwaysIncludeErrorsFromFields.includes(field)) {
823
+ fieldsToUpdate.add(field);
824
+ return;
825
+ }
826
+ if (!prevErrors.has(field)) {
827
+ const fieldTouched = get2().touchedFields[field];
828
+ const formHasBeenSubmitted = get2().hasBeenSubmitted;
829
+ if (fieldTouched || formHasBeenSubmitted)
830
+ fieldsToUpdate.add(field);
831
+ return;
832
+ }
833
+ });
834
+ if (fieldsToDelete.size === 0 && fieldsToUpdate.size === 0) {
835
+ return { ...validationResult, error: { fieldErrors: get2().fieldErrors } };
836
+ }
837
+ set((state) => {
838
+ fieldsToDelete.forEach((field) => {
839
+ delete state.fieldErrors[field];
840
+ });
841
+ fieldsToUpdate.forEach((field) => {
842
+ state.fieldErrors[field] = fieldErrors[field];
843
+ });
844
+ });
845
+ return { ...validationResult, error: { fieldErrors: get2().fieldErrors } };
758
846
  },
759
847
  submit: () => {
760
848
  const formElement = get2().formElement;
@@ -907,12 +995,12 @@ var createFormState = (set, get2) => ({
907
995
  mutateAsArray(
908
996
  fieldName,
909
997
  state.touchedFields,
910
- (array) => insert(array, index, false)
998
+ (array) => insertEmpty(array, index)
911
999
  );
912
1000
  mutateAsArray(
913
1001
  fieldName,
914
1002
  state.fieldErrors,
915
- (array) => insert(array, index, void 0)
1003
+ (array) => insertEmpty(array, index)
916
1004
  );
917
1005
  });
918
1006
  get2().controlledFields.kickoffValueUpdate(fieldName);
@@ -964,12 +1052,12 @@ var createFormState = (set, get2) => ({
964
1052
  mutateAsArray(
965
1053
  fieldName,
966
1054
  state.touchedFields,
967
- (array) => array.unshift(false)
1055
+ (array) => insertEmpty(array, 0)
968
1056
  );
969
1057
  mutateAsArray(
970
1058
  fieldName,
971
1059
  state.fieldErrors,
972
- (array) => array.unshift(void 0)
1060
+ (array) => insertEmpty(array, 0)
973
1061
  );
974
1062
  });
975
1063
  },
@@ -1101,8 +1189,8 @@ var useDefaultValuesForForm = (context) => {
1101
1189
  var useHasActiveFormSubmit = ({
1102
1190
  fetcher
1103
1191
  }) => {
1104
- const transition = (0, import_react2.useTransition)();
1105
- const hasActiveSubmission = fetcher ? fetcher.state === "submitting" : !!transition.submission;
1192
+ let navigation = (0, import_react2.useNavigation)();
1193
+ const hasActiveSubmission = fetcher ? fetcher.state === "submitting" : navigation.state === "submitting";
1106
1194
  return hasActiveSubmission;
1107
1195
  };
1108
1196
  var useFieldTouched = (field, { formId }) => {
@@ -1135,7 +1223,7 @@ var useFieldDefaultValue = (name, context) => {
1135
1223
  var useInternalIsSubmitting = (formId) => useFormStore(formId, (state) => state.isSubmitting);
1136
1224
  var useInternalIsValid = (formId) => useFormStore(formId, (state) => state.isValid());
1137
1225
  var useInternalHasBeenSubmitted = (formId) => useFormStore(formId, (state) => state.hasBeenSubmitted);
1138
- var useValidateField = (formId) => useFormStore(formId, (state) => state.validateField);
1226
+ var useSmartValidate = (formId) => useFormStore(formId, (state) => state.smartValidate);
1139
1227
  var useValidate = (formId) => useFormStore(formId, (state) => state.validate);
1140
1228
  var noOpReceiver = () => () => {
1141
1229
  };
@@ -1246,7 +1334,7 @@ var useField = (name, options) => {
1246
1334
  const error = useFieldError(name, formContext);
1247
1335
  const clearError = useClearError(formContext);
1248
1336
  const hasBeenSubmitted = useInternalHasBeenSubmitted(formContext.formId);
1249
- const validateField = useValidateField(formContext.formId);
1337
+ const smartValidate = useSmartValidate(formContext.formId);
1250
1338
  const registerReceiveFocus = useRegisterReceiveFocus(formContext.formId);
1251
1339
  (0, import_react5.useEffect)(() => {
1252
1340
  if (handleReceiveFocus)
@@ -1256,9 +1344,7 @@ var useField = (name, options) => {
1256
1344
  const helpers = {
1257
1345
  error,
1258
1346
  clearError: () => clearError(name),
1259
- validate: () => {
1260
- validateField(name);
1261
- },
1347
+ validate: () => smartValidate({ alwaysIncludeErrorsFromFields: [name] }),
1262
1348
  defaultValue,
1263
1349
  touched,
1264
1350
  setTouched
@@ -1282,7 +1368,7 @@ var useField = (name, options) => {
1282
1368
  name,
1283
1369
  hasBeenSubmitted,
1284
1370
  options == null ? void 0 : options.validationBehavior,
1285
- validateField
1371
+ smartValidate
1286
1372
  ]);
1287
1373
  return field;
1288
1374
  };
@@ -1495,6 +1581,9 @@ function ValidatedForm({
1495
1581
  method,
1496
1582
  replace: replace2,
1497
1583
  id,
1584
+ preventScrollReset,
1585
+ relative,
1586
+ encType,
1498
1587
  ...rest
1499
1588
  }) {
1500
1589
  var _a;
@@ -1587,11 +1676,11 @@ function ValidatedForm({
1587
1676
  startSubmit();
1588
1677
  const submitter = nativeEvent.submitter;
1589
1678
  const formMethod = (submitter == null ? void 0 : submitter.formMethod) || method;
1590
- const formDataToValidate = getDataFromForm(target);
1679
+ const formData = getDataFromForm(target);
1591
1680
  if (submitter == null ? void 0 : submitter.name) {
1592
- formDataToValidate.append(submitter.name, submitter.value);
1681
+ formData.append(submitter.name, submitter.value);
1593
1682
  }
1594
- const result = await validator.validate(formDataToValidate);
1683
+ const result = await validator.validate(formData);
1595
1684
  if (result.error) {
1596
1685
  setFieldErrors(result.error.fieldErrors);
1597
1686
  endSubmit();
@@ -1610,13 +1699,18 @@ function ValidatedForm({
1610
1699
  endSubmit();
1611
1700
  return;
1612
1701
  }
1702
+ const opts = {
1703
+ method: formMethod,
1704
+ replace: replace2,
1705
+ preventScrollReset,
1706
+ relative,
1707
+ action,
1708
+ encType
1709
+ };
1613
1710
  if (fetcher)
1614
- fetcher.submit(submitter || target, { method: formMethod });
1711
+ fetcher.submit(formData, opts);
1615
1712
  else
1616
- submit(submitter || target, {
1617
- replace: replace2,
1618
- method: formMethod
1619
- });
1713
+ submit(formData, opts);
1620
1714
  }
1621
1715
  };
1622
1716
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1627,7 +1721,10 @@ function ValidatedForm({
1627
1721
  id,
1628
1722
  action,
1629
1723
  method,
1724
+ encType,
1630
1725
  replace: replace2,
1726
+ preventScrollReset,
1727
+ relative,
1631
1728
  onSubmit: (e) => {
1632
1729
  e.preventDefault();
1633
1730
  handleSubmit(
@@ -1745,7 +1842,7 @@ var useFormState = (formId) => {
1745
1842
  var useFormHelpers = (formId) => {
1746
1843
  const formContext = useInternalFormContext(formId, "useFormHelpers");
1747
1844
  const setTouched = useSetTouched(formContext);
1748
- const validateField = useValidateField(formContext.formId);
1845
+ const validateField = useSmartValidate(formContext.formId);
1749
1846
  const validate = useValidate(formContext.formId);
1750
1847
  const clearError = useClearError(formContext);
1751
1848
  const setFieldErrors = useSetFieldErrors(formContext.formId);
@@ -1755,7 +1852,13 @@ var useFormHelpers = (formId) => {
1755
1852
  return (0, import_react11.useMemo)(
1756
1853
  () => ({
1757
1854
  setTouched,
1758
- validateField,
1855
+ validateField: async (fieldName) => {
1856
+ var _a, _b;
1857
+ const res = await validateField({
1858
+ alwaysIncludeErrorsFromFields: [fieldName]
1859
+ });
1860
+ return (_b = (_a = res.error) == null ? void 0 : _a.fieldErrors[fieldName]) != null ? _b : null;
1861
+ },
1759
1862
  clearError,
1760
1863
  validate,
1761
1864
  clearAllErrors: () => setFieldErrors({}),
@@ -1828,6 +1931,7 @@ var useFormContext = (formId) => {
1828
1931
  };
1829
1932
 
1830
1933
  // src/internal/state/fieldArray.tsx
1934
+ var import_nanoid = require("nanoid");
1831
1935
  var import_react13 = require("react");
1832
1936
  var import_react14 = require("react");
1833
1937
  var import_tiny_invariant4 = __toESM(require("tiny-invariant"));
@@ -1836,7 +1940,7 @@ var useInternalFieldArray = (context, field, validationBehavior) => {
1836
1940
  const value = useFieldDefaultValue(field, context);
1837
1941
  useRegisterControlledField(context, field);
1838
1942
  const hasBeenSubmitted = useInternalHasBeenSubmitted(context.formId);
1839
- const validateField = useValidateField(context.formId);
1943
+ const validateField = useSmartValidate(context.formId);
1840
1944
  const error = useFieldError(field, context);
1841
1945
  const resolvedValidationBehavior = {
1842
1946
  initial: "onSubmit",
@@ -1846,7 +1950,7 @@ var useInternalFieldArray = (context, field, validationBehavior) => {
1846
1950
  const behavior = hasBeenSubmitted ? resolvedValidationBehavior.whenSubmitted : resolvedValidationBehavior.initial;
1847
1951
  const maybeValidate = (0, import_react14.useCallback)(() => {
1848
1952
  if (behavior === "onChange") {
1849
- validateField(field);
1953
+ validateField({ alwaysIncludeErrorsFromFields: [field] });
1850
1954
  }
1851
1955
  }, [behavior, field, validateField]);
1852
1956
  (0, import_tiny_invariant4.default)(
@@ -1857,56 +1961,78 @@ var useInternalFieldArray = (context, field, validationBehavior) => {
1857
1961
  context.formId,
1858
1962
  (state) => state.controlledFields.array
1859
1963
  );
1964
+ const arrayValue = (0, import_react13.useMemo)(() => value != null ? value : [], [value]);
1965
+ const keyRef = (0, import_react13.useRef)([]);
1966
+ if (keyRef.current.length !== arrayValue.length) {
1967
+ keyRef.current = arrayValue.map(() => (0, import_nanoid.nanoid)());
1968
+ }
1860
1969
  const helpers = (0, import_react13.useMemo)(
1861
1970
  () => ({
1862
1971
  push: (item) => {
1863
1972
  arr.push(field, item);
1973
+ keyRef.current.push((0, import_nanoid.nanoid)());
1864
1974
  maybeValidate();
1865
1975
  },
1866
1976
  swap: (indexA, indexB) => {
1867
1977
  arr.swap(field, indexA, indexB);
1978
+ swap(keyRef.current, indexA, indexB);
1868
1979
  maybeValidate();
1869
1980
  },
1870
1981
  move: (from2, to) => {
1871
1982
  arr.move(field, from2, to);
1983
+ move(keyRef.current, from2, to);
1872
1984
  maybeValidate();
1873
1985
  },
1874
1986
  insert: (index, value2) => {
1875
1987
  arr.insert(field, index, value2);
1988
+ insert(keyRef.current, index, (0, import_nanoid.nanoid)());
1876
1989
  maybeValidate();
1877
1990
  },
1878
1991
  unshift: (value2) => {
1879
1992
  arr.unshift(field, value2);
1993
+ keyRef.current.unshift((0, import_nanoid.nanoid)());
1880
1994
  maybeValidate();
1881
1995
  },
1882
1996
  remove: (index) => {
1883
1997
  arr.remove(field, index);
1998
+ remove(keyRef.current, index);
1884
1999
  maybeValidate();
1885
2000
  },
1886
2001
  pop: () => {
1887
2002
  arr.pop(field);
2003
+ keyRef.current.pop();
1888
2004
  maybeValidate();
1889
2005
  },
1890
2006
  replace: (index, value2) => {
1891
2007
  arr.replace(field, index, value2);
2008
+ keyRef.current[index] = (0, import_nanoid.nanoid)();
1892
2009
  maybeValidate();
1893
2010
  }
1894
2011
  }),
1895
2012
  [arr, field, maybeValidate]
1896
2013
  );
1897
- const arrayValue = (0, import_react13.useMemo)(() => value != null ? value : [], [value]);
1898
- return [arrayValue, helpers, error];
2014
+ const valueWithKeys = (0, import_react13.useMemo)(() => {
2015
+ const result = [];
2016
+ arrayValue.forEach((item, index) => {
2017
+ result[index] = {
2018
+ key: keyRef.current[index],
2019
+ defaultValue: item
2020
+ };
2021
+ });
2022
+ return result;
2023
+ }, [arrayValue]);
2024
+ return [valueWithKeys, helpers, error];
1899
2025
  };
1900
2026
  function useFieldArray(name, { formId, validationBehavior } = {}) {
1901
2027
  const context = useInternalFormContext(formId, "FieldArray");
1902
2028
  return useInternalFieldArray(context, name, validationBehavior);
1903
2029
  }
1904
- var FieldArray = ({
2030
+ function FieldArray({
1905
2031
  name,
1906
2032
  children,
1907
2033
  formId,
1908
2034
  validationBehavior
1909
- }) => {
2035
+ }) {
1910
2036
  const context = useInternalFormContext(formId, "FieldArray");
1911
2037
  const [value, helpers, error] = useInternalFieldArray(
1912
2038
  context,
@@ -1914,7 +2040,7 @@ var FieldArray = ({
1914
2040
  validationBehavior
1915
2041
  );
1916
2042
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: children(value, helpers, error) });
1917
- };
2043
+ }
1918
2044
  // Annotate the CommonJS export names for ESM import in node:
1919
2045
  0 && (module.exports = {
1920
2046
  FieldArray,