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.
@@ -1,4 +1,5 @@
1
- import React, { useMemo } from "react";
1
+ import { nanoid } from "nanoid";
2
+ import React, { useMemo, useRef, useState } from "react";
2
3
  import { useCallback } from "react";
3
4
  import invariant from "tiny-invariant";
4
5
  import { InternalFormContextValue } from "../formContext";
@@ -7,8 +8,9 @@ import {
7
8
  useFieldError,
8
9
  useInternalFormContext,
9
10
  useInternalHasBeenSubmitted,
10
- useValidateField,
11
+ useSmartValidate,
11
12
  } from "../hooks";
13
+ import * as arrayUtil from "./arrayUtil";
12
14
  import { useRegisterControlledField } from "./controlledFields";
13
15
  import { useFormStore } from "./storeHooks";
14
16
 
@@ -19,6 +21,19 @@ export type FieldArrayValidationBehaviorOptions = {
19
21
  whenSubmitted: FieldArrayValidationBehavior;
20
22
  };
21
23
 
24
+ export type FieldArrayItem<T> = {
25
+ /**
26
+ * The default value of the item.
27
+ * This does not update as the field is changed by the user.
28
+ */
29
+ defaultValue: T;
30
+ /**
31
+ * A unique key for the item.
32
+ * Use this as the key prop when rendering the item.
33
+ */
34
+ key: string;
35
+ };
36
+
22
37
  const useInternalFieldArray = (
23
38
  context: InternalFormContextValue,
24
39
  field: string,
@@ -27,7 +42,7 @@ const useInternalFieldArray = (
27
42
  const value = useFieldDefaultValue(field, context);
28
43
  useRegisterControlledField(context, field);
29
44
  const hasBeenSubmitted = useInternalHasBeenSubmitted(context.formId);
30
- const validateField = useValidateField(context.formId);
45
+ const validateField = useSmartValidate(context.formId);
31
46
  const error = useFieldError(field, context);
32
47
 
33
48
  const resolvedValidationBehavior: FieldArrayValidationBehaviorOptions = {
@@ -42,7 +57,7 @@ const useInternalFieldArray = (
42
57
 
43
58
  const maybeValidate = useCallback(() => {
44
59
  if (behavior === "onChange") {
45
- validateField(field);
60
+ validateField({ alwaysIncludeErrorsFromFields: [field] });
46
61
  }
47
62
  }, [behavior, field, validateField]);
48
63
 
@@ -56,47 +71,74 @@ const useInternalFieldArray = (
56
71
  (state) => state.controlledFields.array
57
72
  );
58
73
 
74
+ const arrayValue = useMemo<unknown[]>(() => value ?? [], [value]);
75
+ const keyRef = useRef<string[]>([]);
76
+
77
+ // If the lengths don't match up it means one of two things
78
+ // 1. The array has been modified outside of this hook
79
+ // 2. We're initializing the array
80
+ if (keyRef.current.length !== arrayValue.length) {
81
+ keyRef.current = arrayValue.map(() => nanoid());
82
+ }
83
+
59
84
  const helpers = useMemo(
60
85
  () => ({
61
86
  push: (item: any) => {
62
87
  arr.push(field, item);
88
+ keyRef.current.push(nanoid());
63
89
  maybeValidate();
64
90
  },
65
91
  swap: (indexA: number, indexB: number) => {
66
92
  arr.swap(field, indexA, indexB);
93
+ arrayUtil.swap(keyRef.current, indexA, indexB);
67
94
  maybeValidate();
68
95
  },
69
96
  move: (from: number, to: number) => {
70
97
  arr.move(field, from, to);
98
+ arrayUtil.move(keyRef.current, from, to);
71
99
  maybeValidate();
72
100
  },
73
101
  insert: (index: number, value: any) => {
74
102
  arr.insert(field, index, value);
103
+ arrayUtil.insert(keyRef.current, index, nanoid());
75
104
  maybeValidate();
76
105
  },
77
106
  unshift: (value: any) => {
78
107
  arr.unshift(field, value);
108
+ keyRef.current.unshift(nanoid());
79
109
  maybeValidate();
80
110
  },
81
111
  remove: (index: number) => {
82
112
  arr.remove(field, index);
113
+ arrayUtil.remove(keyRef.current, index);
83
114
  maybeValidate();
84
115
  },
85
116
  pop: () => {
86
117
  arr.pop(field);
118
+ keyRef.current.pop();
87
119
  maybeValidate();
88
120
  },
89
121
  replace: (index: number, value: any) => {
90
122
  arr.replace(field, index, value);
123
+ keyRef.current[index] = nanoid();
91
124
  maybeValidate();
92
125
  },
93
126
  }),
94
127
  [arr, field, maybeValidate]
95
128
  );
96
129
 
97
- const arrayValue = useMemo(() => value ?? [], [value]);
98
-
99
- return [arrayValue, helpers, error] as const;
130
+ const valueWithKeys = useMemo(() => {
131
+ const result: { defaultValue: any; key: string }[] = [];
132
+ arrayValue.forEach((item, index) => {
133
+ result[index] = {
134
+ key: keyRef.current[index],
135
+ defaultValue: item,
136
+ };
137
+ });
138
+ return result;
139
+ }, [arrayValue]);
140
+
141
+ return [valueWithKeys, helpers, error] as const;
100
142
  };
101
143
 
102
144
  export type FieldArrayHelpers<Item = any> = {
@@ -122,29 +164,29 @@ export function useFieldArray<Item = any>(
122
164
  const context = useInternalFormContext(formId, "FieldArray");
123
165
 
124
166
  return useInternalFieldArray(context, name, validationBehavior) as [
125
- itemDefaults: Item[],
167
+ items: FieldArrayItem<Item>[],
126
168
  helpers: FieldArrayHelpers,
127
169
  error: string | undefined
128
170
  ];
129
171
  }
130
172
 
131
- export type FieldArrayProps = {
173
+ export type FieldArrayProps<Item> = {
132
174
  name: string;
133
175
  children: (
134
- itemDefaults: any[],
135
- helpers: FieldArrayHelpers,
176
+ items: FieldArrayItem<Item>[],
177
+ helpers: FieldArrayHelpers<Item>,
136
178
  error: string | undefined
137
179
  ) => React.ReactNode;
138
180
  formId?: string;
139
181
  validationBehavior?: FieldArrayValidationBehaviorOptions;
140
182
  };
141
183
 
142
- export const FieldArray = ({
184
+ export function FieldArray<Item = any>({
143
185
  name,
144
186
  children,
145
187
  formId,
146
188
  validationBehavior,
147
- }: FieldArrayProps) => {
189
+ }: FieldArrayProps<Item>) {
148
190
  const context = useInternalFormContext(formId, "FieldArray");
149
191
  const [value, helpers, error] = useInternalFieldArray(
150
192
  context,
@@ -152,4 +194,4 @@ export const FieldArray = ({
152
194
  validationBehavior
153
195
  );
154
196
  return <>{children(value, helpers, error)}</>;
155
- };
197
+ }
package/src/server.ts CHANGED
@@ -42,6 +42,9 @@ export type FormDefaults = {
42
42
  [formDefaultsKey: `${typeof FORM_DEFAULTS_FIELD}_${string}`]: any;
43
43
  };
44
44
 
45
+ // FIXME: Remove after https://github.com/egoist/tsup/issues/813 is fixed
46
+ export type internal_FORM_DEFAULTS_FIELD = typeof FORM_DEFAULTS_FIELD;
47
+
45
48
  export const setFormDefaults = <DataType = any>(
46
49
  formId: string,
47
50
  defaultValues: Partial<DataType>
@@ -11,7 +11,6 @@ import {
11
11
  useTouchedFields,
12
12
  useInternalIsValid,
13
13
  useFieldErrors,
14
- useValidateField,
15
14
  useValidate,
16
15
  useSetFieldErrors,
17
16
  useResetFormElement,
@@ -20,6 +19,7 @@ import {
20
19
  useFormSubactionProp,
21
20
  useSubmitForm,
22
21
  useFormValues,
22
+ useSmartValidate,
23
23
  } from "../internal/hooks";
24
24
  import {
25
25
  FieldErrors,
@@ -133,7 +133,7 @@ export type FormHelpers = {
133
133
  export const useFormHelpers = (formId?: string): FormHelpers => {
134
134
  const formContext = useInternalFormContext(formId, "useFormHelpers");
135
135
  const setTouched = useSetTouched(formContext);
136
- const validateField = useValidateField(formContext.formId);
136
+ const validateField = useSmartValidate(formContext.formId);
137
137
  const validate = useValidate(formContext.formId);
138
138
  const clearError = useClearError(formContext);
139
139
  const setFieldErrors = useSetFieldErrors(formContext.formId);
@@ -143,7 +143,12 @@ export const useFormHelpers = (formId?: string): FormHelpers => {
143
143
  return useMemo(
144
144
  () => ({
145
145
  setTouched,
146
- validateField,
146
+ validateField: async (fieldName: string) => {
147
+ const res = await validateField({
148
+ alwaysIncludeErrorsFromFields: [fieldName],
149
+ });
150
+ return res.error?.fieldErrors[fieldName] ?? null;
151
+ },
147
152
  clearError,
148
153
  validate,
149
154
  clearAllErrors: () => setFieldErrors({}),
@@ -44,7 +44,10 @@ export type Validator<DataType> = {
44
44
  validate: (
45
45
  unvalidatedData: GenericObject
46
46
  ) => Promise<ValidationResult<DataType>>;
47
- validateField: (
47
+ /**
48
+ * @deprecated Will be removed in a future version of remix-validated-form
49
+ */
50
+ validateField?: (
48
51
  unvalidatedData: GenericObject,
49
52
  field: string
50
53
  ) => Promise<ValidateFieldResult>;