remix-validated-form 4.6.11 → 5.0.0
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/.turbo/turbo-build.log +8 -8
- package/README.md +27 -30
- package/dist/index.cjs.js +178 -52
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +38 -16
- package/dist/index.esm.js +180 -54
- package/dist/index.esm.js.map +1 -1
- package/package.json +6 -5
- package/src/ValidatedForm.tsx +44 -14
- package/src/hooks.ts +5 -7
- package/src/internal/getInputProps.ts +5 -5
- package/src/internal/hooks.ts +5 -5
- package/src/internal/state/arrayUtil.ts +41 -3
- package/src/internal/state/createFormStore.ts +104 -27
- package/src/internal/state/fieldArray.tsx +56 -14
- package/src/server.ts +3 -0
- package/src/unreleased/formStateHooks.ts +8 -3
- package/src/validation/types.ts +4 -1
@@ -1,4 +1,5 @@
|
|
1
|
-
import
|
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
|
-
|
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 =
|
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
|
98
|
-
|
99
|
-
|
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
|
-
|
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
|
-
|
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
|
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 =
|
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({}),
|
package/src/validation/types.ts
CHANGED
@@ -44,7 +44,10 @@ export type Validator<DataType> = {
|
|
44
44
|
validate: (
|
45
45
|
unvalidatedData: GenericObject
|
46
46
|
) => Promise<ValidationResult<DataType>>;
|
47
|
-
|
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>;
|