remix-validated-form 5.0.2 → 5.1.1-beta.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 +152 -8
- package/dist/index.cjs.js +898 -63
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.esm.js +876 -15
- package/dist/index.esm.js.map +1 -1
- package/package.json +4 -4
- package/src/ValidatedForm.tsx +0 -427
- package/src/hooks.ts +0 -160
- package/src/index.ts +0 -12
- package/src/internal/MultiValueMap.ts +0 -44
- package/src/internal/constants.ts +0 -4
- package/src/internal/flatten.ts +0 -12
- package/src/internal/formContext.ts +0 -13
- package/src/internal/getInputProps.test.ts +0 -251
- package/src/internal/getInputProps.ts +0 -94
- package/src/internal/hooks.ts +0 -217
- package/src/internal/hydratable.ts +0 -28
- package/src/internal/logic/getCheckboxChecked.ts +0 -10
- package/src/internal/logic/getRadioChecked.ts +0 -18
- package/src/internal/logic/nestedObjectToPathObject.ts +0 -63
- package/src/internal/logic/requestSubmit.test.tsx +0 -24
- package/src/internal/logic/requestSubmit.ts +0 -103
- package/src/internal/state/arrayUtil.ts +0 -451
- package/src/internal/state/controlledFields.ts +0 -86
- package/src/internal/state/createFormStore.ts +0 -591
- package/src/internal/state/fieldArray.tsx +0 -197
- package/src/internal/state/storeHooks.ts +0 -9
- package/src/internal/state/types.ts +0 -1
- package/src/internal/submissionCallbacks.ts +0 -15
- package/src/internal/util.ts +0 -39
- package/src/server.ts +0 -53
- package/src/unreleased/formStateHooks.ts +0 -170
- package/src/userFacingFormContext.ts +0 -147
- package/src/validation/createValidator.ts +0 -53
- package/src/validation/types.ts +0 -72
- package/tsconfig.json +0 -8
@@ -1,251 +0,0 @@
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
2
|
-
import {
|
3
|
-
createGetInputProps,
|
4
|
-
CreateGetInputPropsOptions,
|
5
|
-
} from "./getInputProps";
|
6
|
-
|
7
|
-
const fakeEvent = { fake: "event" } as any;
|
8
|
-
|
9
|
-
describe("getInputProps", () => {
|
10
|
-
describe("initial", () => {
|
11
|
-
it("should validate on blur by default", () => {
|
12
|
-
const options: CreateGetInputPropsOptions = {
|
13
|
-
name: "some-field",
|
14
|
-
defaultValue: "test default value",
|
15
|
-
touched: false,
|
16
|
-
hasBeenSubmitted: false,
|
17
|
-
setTouched: vi.fn(),
|
18
|
-
clearError: vi.fn(),
|
19
|
-
validate: vi.fn(),
|
20
|
-
};
|
21
|
-
const getInputProps = createGetInputProps(options);
|
22
|
-
|
23
|
-
const provided = {
|
24
|
-
onBlur: vi.fn(),
|
25
|
-
onChange: vi.fn(),
|
26
|
-
};
|
27
|
-
const { onChange, onBlur } = getInputProps(provided);
|
28
|
-
|
29
|
-
onChange!(fakeEvent);
|
30
|
-
expect(provided.onChange).toBeCalledTimes(1);
|
31
|
-
expect(provided.onChange).toBeCalledWith(fakeEvent);
|
32
|
-
expect(options.setTouched).not.toBeCalled();
|
33
|
-
expect(options.validate).not.toBeCalled();
|
34
|
-
|
35
|
-
onBlur!(fakeEvent);
|
36
|
-
expect(provided.onBlur).toBeCalledTimes(1);
|
37
|
-
expect(provided.onBlur).toBeCalledWith(fakeEvent);
|
38
|
-
expect(options.setTouched).toBeCalledTimes(1);
|
39
|
-
expect(options.setTouched).toBeCalledWith(true);
|
40
|
-
expect(options.validate).toBeCalledTimes(1);
|
41
|
-
});
|
42
|
-
|
43
|
-
it("should respect provided validation behavior", () => {
|
44
|
-
const options: CreateGetInputPropsOptions = {
|
45
|
-
name: "some-field",
|
46
|
-
defaultValue: "test default value",
|
47
|
-
touched: false,
|
48
|
-
hasBeenSubmitted: false,
|
49
|
-
setTouched: vi.fn(),
|
50
|
-
clearError: vi.fn(),
|
51
|
-
validate: vi.fn(),
|
52
|
-
validationBehavior: {
|
53
|
-
initial: "onChange",
|
54
|
-
},
|
55
|
-
};
|
56
|
-
const getInputProps = createGetInputProps(options);
|
57
|
-
|
58
|
-
const provided = {
|
59
|
-
onBlur: vi.fn(),
|
60
|
-
onChange: vi.fn(),
|
61
|
-
};
|
62
|
-
const { onChange, onBlur } = getInputProps(provided);
|
63
|
-
|
64
|
-
onChange!(fakeEvent);
|
65
|
-
expect(provided.onChange).toBeCalledTimes(1);
|
66
|
-
expect(provided.onChange).toBeCalledWith(fakeEvent);
|
67
|
-
expect(options.setTouched).not.toBeCalled();
|
68
|
-
expect(options.validate).toBeCalledTimes(1);
|
69
|
-
|
70
|
-
onBlur!(fakeEvent);
|
71
|
-
expect(provided.onBlur).toBeCalledTimes(1);
|
72
|
-
expect(provided.onBlur).toBeCalledWith(fakeEvent);
|
73
|
-
expect(options.setTouched).toBeCalledTimes(1);
|
74
|
-
expect(options.setTouched).toBeCalledWith(true);
|
75
|
-
expect(options.validate).toBeCalledTimes(1);
|
76
|
-
});
|
77
|
-
|
78
|
-
it("should not validate when behavior is onSubmit", () => {
|
79
|
-
const options: CreateGetInputPropsOptions = {
|
80
|
-
name: "some-field",
|
81
|
-
defaultValue: "test default value",
|
82
|
-
touched: false,
|
83
|
-
hasBeenSubmitted: false,
|
84
|
-
setTouched: vi.fn(),
|
85
|
-
clearError: vi.fn(),
|
86
|
-
validate: vi.fn(),
|
87
|
-
validationBehavior: {
|
88
|
-
initial: "onSubmit",
|
89
|
-
},
|
90
|
-
};
|
91
|
-
const getInputProps = createGetInputProps(options);
|
92
|
-
|
93
|
-
const provided = {
|
94
|
-
onBlur: vi.fn(),
|
95
|
-
onChange: vi.fn(),
|
96
|
-
};
|
97
|
-
const { onChange, onBlur } = getInputProps(provided);
|
98
|
-
|
99
|
-
onChange!(fakeEvent);
|
100
|
-
expect(provided.onChange).toBeCalledTimes(1);
|
101
|
-
expect(provided.onChange).toBeCalledWith(fakeEvent);
|
102
|
-
expect(options.setTouched).not.toBeCalled();
|
103
|
-
expect(options.validate).not.toBeCalled();
|
104
|
-
|
105
|
-
onBlur!(fakeEvent);
|
106
|
-
expect(provided.onBlur).toBeCalledTimes(1);
|
107
|
-
expect(provided.onBlur).toBeCalledWith(fakeEvent);
|
108
|
-
expect(options.setTouched).toBeCalledTimes(1);
|
109
|
-
expect(options.setTouched).toBeCalledWith(true);
|
110
|
-
expect(options.validate).not.toBeCalled();
|
111
|
-
});
|
112
|
-
});
|
113
|
-
|
114
|
-
describe("whenTouched", () => {
|
115
|
-
it("should validate on change by default", () => {
|
116
|
-
const options: CreateGetInputPropsOptions = {
|
117
|
-
name: "some-field",
|
118
|
-
defaultValue: "test default value",
|
119
|
-
touched: true,
|
120
|
-
hasBeenSubmitted: false,
|
121
|
-
setTouched: vi.fn(),
|
122
|
-
clearError: vi.fn(),
|
123
|
-
validate: vi.fn(),
|
124
|
-
};
|
125
|
-
const getInputProps = createGetInputProps(options);
|
126
|
-
|
127
|
-
const provided = {
|
128
|
-
onBlur: vi.fn(),
|
129
|
-
onChange: vi.fn(),
|
130
|
-
};
|
131
|
-
const { onChange, onBlur } = getInputProps(provided);
|
132
|
-
|
133
|
-
onChange!(fakeEvent);
|
134
|
-
expect(provided.onChange).toBeCalledTimes(1);
|
135
|
-
expect(provided.onChange).toBeCalledWith(fakeEvent);
|
136
|
-
expect(options.setTouched).not.toBeCalled();
|
137
|
-
expect(options.validate).toBeCalledTimes(1);
|
138
|
-
|
139
|
-
onBlur!(fakeEvent);
|
140
|
-
expect(provided.onBlur).toBeCalledTimes(1);
|
141
|
-
expect(provided.onBlur).toBeCalledWith(fakeEvent);
|
142
|
-
expect(options.setTouched).toBeCalledTimes(1);
|
143
|
-
expect(options.setTouched).toBeCalledWith(true);
|
144
|
-
expect(options.validate).toBeCalledTimes(1);
|
145
|
-
});
|
146
|
-
|
147
|
-
it("should respect provided validation behavior", () => {
|
148
|
-
const options: CreateGetInputPropsOptions = {
|
149
|
-
name: "some-field",
|
150
|
-
defaultValue: "test default value",
|
151
|
-
touched: true,
|
152
|
-
hasBeenSubmitted: false,
|
153
|
-
setTouched: vi.fn(),
|
154
|
-
clearError: vi.fn(),
|
155
|
-
validate: vi.fn(),
|
156
|
-
validationBehavior: {
|
157
|
-
whenTouched: "onBlur",
|
158
|
-
},
|
159
|
-
};
|
160
|
-
const getInputProps = createGetInputProps(options);
|
161
|
-
|
162
|
-
const provided = {
|
163
|
-
onBlur: vi.fn(),
|
164
|
-
onChange: vi.fn(),
|
165
|
-
};
|
166
|
-
const { onChange, onBlur } = getInputProps(provided);
|
167
|
-
|
168
|
-
onChange!(fakeEvent);
|
169
|
-
expect(provided.onChange).toBeCalledTimes(1);
|
170
|
-
expect(provided.onChange).toBeCalledWith(fakeEvent);
|
171
|
-
expect(options.setTouched).not.toBeCalled();
|
172
|
-
expect(options.validate).not.toBeCalled();
|
173
|
-
|
174
|
-
onBlur!(fakeEvent);
|
175
|
-
expect(provided.onBlur).toBeCalledTimes(1);
|
176
|
-
expect(provided.onBlur).toBeCalledWith(fakeEvent);
|
177
|
-
expect(options.setTouched).toBeCalledTimes(1);
|
178
|
-
expect(options.setTouched).toBeCalledWith(true);
|
179
|
-
expect(options.validate).toBeCalledTimes(1);
|
180
|
-
});
|
181
|
-
});
|
182
|
-
|
183
|
-
describe("whenSubmitted", () => {
|
184
|
-
it("should validate on change by default", () => {
|
185
|
-
const options: CreateGetInputPropsOptions = {
|
186
|
-
name: "some-field",
|
187
|
-
defaultValue: "test default value",
|
188
|
-
touched: true,
|
189
|
-
hasBeenSubmitted: true,
|
190
|
-
setTouched: vi.fn(),
|
191
|
-
clearError: vi.fn(),
|
192
|
-
validate: vi.fn(),
|
193
|
-
};
|
194
|
-
const getInputProps = createGetInputProps(options);
|
195
|
-
|
196
|
-
const provided = {
|
197
|
-
onBlur: vi.fn(),
|
198
|
-
onChange: vi.fn(),
|
199
|
-
};
|
200
|
-
const { onChange, onBlur } = getInputProps(provided);
|
201
|
-
|
202
|
-
onChange!(fakeEvent);
|
203
|
-
expect(provided.onChange).toBeCalledTimes(1);
|
204
|
-
expect(provided.onChange).toBeCalledWith(fakeEvent);
|
205
|
-
expect(options.setTouched).not.toBeCalled();
|
206
|
-
expect(options.validate).toBeCalledTimes(1);
|
207
|
-
|
208
|
-
onBlur!(fakeEvent);
|
209
|
-
expect(provided.onBlur).toBeCalledTimes(1);
|
210
|
-
expect(provided.onBlur).toBeCalledWith(fakeEvent);
|
211
|
-
expect(options.setTouched).toBeCalledTimes(1);
|
212
|
-
expect(options.setTouched).toBeCalledWith(true);
|
213
|
-
expect(options.validate).toBeCalledTimes(1);
|
214
|
-
});
|
215
|
-
|
216
|
-
it("should respect provided validation behavior", () => {
|
217
|
-
const options: CreateGetInputPropsOptions = {
|
218
|
-
name: "some-field",
|
219
|
-
defaultValue: "test default value",
|
220
|
-
touched: true,
|
221
|
-
hasBeenSubmitted: true,
|
222
|
-
setTouched: vi.fn(),
|
223
|
-
clearError: vi.fn(),
|
224
|
-
validate: vi.fn(),
|
225
|
-
validationBehavior: {
|
226
|
-
whenSubmitted: "onBlur",
|
227
|
-
},
|
228
|
-
};
|
229
|
-
const getInputProps = createGetInputProps(options);
|
230
|
-
|
231
|
-
const provided = {
|
232
|
-
onBlur: vi.fn(),
|
233
|
-
onChange: vi.fn(),
|
234
|
-
};
|
235
|
-
const { onChange, onBlur } = getInputProps(provided);
|
236
|
-
|
237
|
-
onChange!(fakeEvent);
|
238
|
-
expect(provided.onChange).toBeCalledTimes(1);
|
239
|
-
expect(provided.onChange).toBeCalledWith(fakeEvent);
|
240
|
-
expect(options.setTouched).not.toBeCalled();
|
241
|
-
expect(options.validate).not.toBeCalled();
|
242
|
-
|
243
|
-
onBlur!(fakeEvent);
|
244
|
-
expect(provided.onBlur).toBeCalledTimes(1);
|
245
|
-
expect(provided.onBlur).toBeCalledWith(fakeEvent);
|
246
|
-
expect(options.setTouched).toBeCalledTimes(1);
|
247
|
-
expect(options.setTouched).toBeCalledWith(true);
|
248
|
-
expect(options.validate).toBeCalledTimes(1);
|
249
|
-
});
|
250
|
-
});
|
251
|
-
});
|
@@ -1,94 +0,0 @@
|
|
1
|
-
import * as R from "remeda";
|
2
|
-
import { getCheckboxChecked } from "./logic/getCheckboxChecked";
|
3
|
-
import { getRadioChecked } from "./logic/getRadioChecked";
|
4
|
-
|
5
|
-
export type ValidationBehavior = "onBlur" | "onChange" | "onSubmit";
|
6
|
-
|
7
|
-
export type ValidationBehaviorOptions = {
|
8
|
-
initial: ValidationBehavior;
|
9
|
-
whenTouched: ValidationBehavior;
|
10
|
-
whenSubmitted: ValidationBehavior;
|
11
|
-
};
|
12
|
-
|
13
|
-
export type CreateGetInputPropsOptions = {
|
14
|
-
clearError: () => void;
|
15
|
-
validate: () => void;
|
16
|
-
defaultValue?: any;
|
17
|
-
touched: boolean;
|
18
|
-
setTouched: (touched: boolean) => void;
|
19
|
-
hasBeenSubmitted: boolean;
|
20
|
-
validationBehavior?: Partial<ValidationBehaviorOptions>;
|
21
|
-
name: string;
|
22
|
-
};
|
23
|
-
|
24
|
-
type HandledProps = "name" | "defaultValue" | "defaultChecked";
|
25
|
-
type Callbacks = "onChange" | "onBlur";
|
26
|
-
|
27
|
-
type MinimalInputProps = {
|
28
|
-
onChange?: ((...args: any[]) => void) | undefined;
|
29
|
-
onBlur?: ((...args: any[]) => void) | undefined;
|
30
|
-
defaultValue?: any;
|
31
|
-
defaultChecked?: boolean | undefined;
|
32
|
-
name?: string | undefined;
|
33
|
-
type?: string | undefined;
|
34
|
-
};
|
35
|
-
|
36
|
-
export type GetInputProps = <T extends MinimalInputProps>(
|
37
|
-
props?: Omit<T, HandledProps | Callbacks> & Partial<Pick<T, Callbacks>>
|
38
|
-
) => T;
|
39
|
-
|
40
|
-
const defaultValidationBehavior: ValidationBehaviorOptions = {
|
41
|
-
initial: "onBlur",
|
42
|
-
whenTouched: "onChange",
|
43
|
-
whenSubmitted: "onChange",
|
44
|
-
};
|
45
|
-
|
46
|
-
export const createGetInputProps = ({
|
47
|
-
clearError,
|
48
|
-
validate,
|
49
|
-
defaultValue,
|
50
|
-
touched,
|
51
|
-
setTouched,
|
52
|
-
hasBeenSubmitted,
|
53
|
-
validationBehavior,
|
54
|
-
name,
|
55
|
-
}: CreateGetInputPropsOptions): GetInputProps => {
|
56
|
-
const validationBehaviors = {
|
57
|
-
...defaultValidationBehavior,
|
58
|
-
...validationBehavior,
|
59
|
-
};
|
60
|
-
|
61
|
-
return <T extends MinimalInputProps>(props = {} as any) => {
|
62
|
-
const behavior = hasBeenSubmitted
|
63
|
-
? validationBehaviors.whenSubmitted
|
64
|
-
: touched
|
65
|
-
? validationBehaviors.whenTouched
|
66
|
-
: validationBehaviors.initial;
|
67
|
-
|
68
|
-
const inputProps: MinimalInputProps = {
|
69
|
-
...props,
|
70
|
-
onChange: (...args: unknown[]) => {
|
71
|
-
if (behavior === "onChange") validate();
|
72
|
-
else clearError();
|
73
|
-
return props?.onChange?.(...args);
|
74
|
-
},
|
75
|
-
onBlur: (...args: unknown[]) => {
|
76
|
-
if (behavior === "onBlur") validate();
|
77
|
-
setTouched(true);
|
78
|
-
return props?.onBlur?.(...args);
|
79
|
-
},
|
80
|
-
name,
|
81
|
-
};
|
82
|
-
|
83
|
-
if (props.type === "checkbox") {
|
84
|
-
inputProps.defaultChecked = getCheckboxChecked(props.value, defaultValue);
|
85
|
-
} else if (props.type === "radio") {
|
86
|
-
inputProps.defaultChecked = getRadioChecked(props.value, defaultValue);
|
87
|
-
} else if (props.value === undefined) {
|
88
|
-
// We should only set the defaultValue if the input is uncontrolled.
|
89
|
-
inputProps.defaultValue = defaultValue;
|
90
|
-
}
|
91
|
-
|
92
|
-
return R.omitBy(inputProps, (value) => value === undefined) as T;
|
93
|
-
};
|
94
|
-
};
|
package/src/internal/hooks.ts
DELETED
@@ -1,217 +0,0 @@
|
|
1
|
-
import { useActionData, useMatches, useNavigation } from "@remix-run/react";
|
2
|
-
import { useCallback, useContext } from "react";
|
3
|
-
import { getPath } from "set-get";
|
4
|
-
import invariant from "tiny-invariant";
|
5
|
-
import { FieldErrors, ValidationErrorResponseData } from "..";
|
6
|
-
import { formDefaultValuesKey } from "./constants";
|
7
|
-
import { InternalFormContext, InternalFormContextValue } from "./formContext";
|
8
|
-
import { Hydratable, hydratable } from "./hydratable";
|
9
|
-
import { useFormStore } from "./state/storeHooks";
|
10
|
-
import { InternalFormId } from "./state/types";
|
11
|
-
|
12
|
-
export const useInternalFormContext = (
|
13
|
-
formId?: string | symbol,
|
14
|
-
hookName?: string
|
15
|
-
) => {
|
16
|
-
const formContext = useContext(InternalFormContext);
|
17
|
-
|
18
|
-
if (formId) return { formId };
|
19
|
-
if (formContext) return formContext;
|
20
|
-
|
21
|
-
throw new Error(
|
22
|
-
`Unable to determine form for ${hookName}. Please use it inside a ValidatedForm or pass a 'formId'.`
|
23
|
-
);
|
24
|
-
};
|
25
|
-
|
26
|
-
export function useErrorResponseForForm({
|
27
|
-
fetcher,
|
28
|
-
subaction,
|
29
|
-
formId,
|
30
|
-
}: InternalFormContextValue): ValidationErrorResponseData | null {
|
31
|
-
const actionData = useActionData<any>();
|
32
|
-
if (fetcher) {
|
33
|
-
if ((fetcher.data as any)?.fieldErrors) return fetcher.data as any;
|
34
|
-
return null;
|
35
|
-
}
|
36
|
-
|
37
|
-
if (!actionData?.fieldErrors) return null;
|
38
|
-
|
39
|
-
// If there's an explicit id, we should ignore data that has the wrong id
|
40
|
-
if (typeof formId === "string" && actionData.formId)
|
41
|
-
return actionData.formId === formId ? actionData : null;
|
42
|
-
|
43
|
-
if (
|
44
|
-
(!subaction && !actionData.subaction) ||
|
45
|
-
actionData.subaction === subaction
|
46
|
-
)
|
47
|
-
return actionData;
|
48
|
-
|
49
|
-
return null;
|
50
|
-
}
|
51
|
-
|
52
|
-
export const useFieldErrorsForForm = (
|
53
|
-
context: InternalFormContextValue
|
54
|
-
): Hydratable<FieldErrors | undefined> => {
|
55
|
-
const response = useErrorResponseForForm(context);
|
56
|
-
const hydrated = useFormStore(context.formId, (state) => state.isHydrated);
|
57
|
-
return hydratable.from(response?.fieldErrors, hydrated);
|
58
|
-
};
|
59
|
-
|
60
|
-
export const useDefaultValuesFromLoader = ({
|
61
|
-
formId,
|
62
|
-
}: InternalFormContextValue) => {
|
63
|
-
const matches = useMatches();
|
64
|
-
if (typeof formId === "string") {
|
65
|
-
const dataKey = formDefaultValuesKey(formId);
|
66
|
-
// If multiple loaders declare the same default values,
|
67
|
-
// we should use the data from the deepest route.
|
68
|
-
const match = matches
|
69
|
-
.reverse()
|
70
|
-
.find(
|
71
|
-
(match) =>
|
72
|
-
match.data && typeof match.data === "object" && dataKey in match.data
|
73
|
-
);
|
74
|
-
return match?.data[dataKey];
|
75
|
-
}
|
76
|
-
|
77
|
-
return null;
|
78
|
-
};
|
79
|
-
|
80
|
-
export const useDefaultValuesForForm = (
|
81
|
-
context: InternalFormContextValue
|
82
|
-
): Hydratable<{ [fieldName: string]: any }> => {
|
83
|
-
const { formId, defaultValuesProp } = context;
|
84
|
-
const hydrated = useFormStore(formId, (state) => state.isHydrated);
|
85
|
-
const errorResponse = useErrorResponseForForm(context);
|
86
|
-
const defaultValuesFromLoader = useDefaultValuesFromLoader(context);
|
87
|
-
|
88
|
-
// Typical flow is:
|
89
|
-
// - Default values only available from props or server
|
90
|
-
// - Props have a higher priority than server
|
91
|
-
// - State gets hydrated with default values
|
92
|
-
// - After submit, we may need to use values from the error
|
93
|
-
|
94
|
-
if (hydrated) return hydratable.hydratedData();
|
95
|
-
if (errorResponse?.repopulateFields) {
|
96
|
-
invariant(
|
97
|
-
typeof errorResponse.repopulateFields === "object",
|
98
|
-
"repopulateFields returned something other than an object"
|
99
|
-
);
|
100
|
-
return hydratable.serverData(errorResponse.repopulateFields);
|
101
|
-
}
|
102
|
-
if (defaultValuesProp) return hydratable.serverData(defaultValuesProp);
|
103
|
-
|
104
|
-
return hydratable.serverData(defaultValuesFromLoader);
|
105
|
-
};
|
106
|
-
|
107
|
-
export const useHasActiveFormSubmit = ({
|
108
|
-
fetcher,
|
109
|
-
}: InternalFormContextValue): boolean => {
|
110
|
-
let navigation = useNavigation();
|
111
|
-
const hasActiveSubmission = fetcher
|
112
|
-
? fetcher.state === "submitting"
|
113
|
-
: navigation.state === "submitting" || navigation.state === "loading";
|
114
|
-
return hasActiveSubmission;
|
115
|
-
};
|
116
|
-
|
117
|
-
export const useFieldTouched = (
|
118
|
-
field: string,
|
119
|
-
{ formId }: InternalFormContextValue
|
120
|
-
) => {
|
121
|
-
const touched = useFormStore(formId, (state) => state.touchedFields[field]);
|
122
|
-
const setFieldTouched = useFormStore(formId, (state) => state.setTouched);
|
123
|
-
const setTouched = useCallback(
|
124
|
-
(touched: boolean) => setFieldTouched(field, touched),
|
125
|
-
[field, setFieldTouched]
|
126
|
-
);
|
127
|
-
return [touched, setTouched] as const;
|
128
|
-
};
|
129
|
-
|
130
|
-
export const useFieldError = (
|
131
|
-
name: string,
|
132
|
-
context: InternalFormContextValue
|
133
|
-
) => {
|
134
|
-
const fieldErrors = useFieldErrorsForForm(context);
|
135
|
-
const state = useFormStore(
|
136
|
-
context.formId,
|
137
|
-
(state) => state.fieldErrors[name]
|
138
|
-
);
|
139
|
-
return fieldErrors.map((fieldErrors) => fieldErrors?.[name]).hydrateTo(state);
|
140
|
-
};
|
141
|
-
|
142
|
-
export const useClearError = (context: InternalFormContextValue) => {
|
143
|
-
const { formId } = context;
|
144
|
-
return useFormStore(formId, (state) => state.clearFieldError);
|
145
|
-
};
|
146
|
-
|
147
|
-
export const useCurrentDefaultValueForField = (
|
148
|
-
formId: InternalFormId,
|
149
|
-
field: string
|
150
|
-
) =>
|
151
|
-
useFormStore(formId, (state) => getPath(state.currentDefaultValues, field));
|
152
|
-
|
153
|
-
export const useFieldDefaultValue = (
|
154
|
-
name: string,
|
155
|
-
context: InternalFormContextValue
|
156
|
-
) => {
|
157
|
-
const defaultValues = useDefaultValuesForForm(context);
|
158
|
-
const state = useCurrentDefaultValueForField(context.formId, name);
|
159
|
-
|
160
|
-
return defaultValues.map((val) => getPath(val, name)).hydrateTo(state);
|
161
|
-
};
|
162
|
-
|
163
|
-
export const useInternalIsSubmitting = (formId: InternalFormId) =>
|
164
|
-
useFormStore(formId, (state) => state.isSubmitting);
|
165
|
-
|
166
|
-
export const useInternalIsValid = (formId: InternalFormId) =>
|
167
|
-
useFormStore(formId, (state) => state.isValid());
|
168
|
-
|
169
|
-
export const useInternalHasBeenSubmitted = (formId: InternalFormId) =>
|
170
|
-
useFormStore(formId, (state) => state.hasBeenSubmitted);
|
171
|
-
|
172
|
-
export const useSmartValidate = (formId: InternalFormId) =>
|
173
|
-
useFormStore(formId, (state) => state.smartValidate);
|
174
|
-
|
175
|
-
export const useValidate = (formId: InternalFormId) =>
|
176
|
-
useFormStore(formId, (state) => state.validate);
|
177
|
-
|
178
|
-
const noOpReceiver = () => () => {};
|
179
|
-
export const useRegisterReceiveFocus = (formId: InternalFormId) =>
|
180
|
-
useFormStore(
|
181
|
-
formId,
|
182
|
-
(state) => state.formProps?.registerReceiveFocus ?? noOpReceiver
|
183
|
-
);
|
184
|
-
|
185
|
-
const defaultDefaultValues = {};
|
186
|
-
export const useSyncedDefaultValues = (formId: InternalFormId) =>
|
187
|
-
useFormStore(
|
188
|
-
formId,
|
189
|
-
(state) => state.formProps?.defaultValues ?? defaultDefaultValues
|
190
|
-
);
|
191
|
-
|
192
|
-
export const useSetTouched = ({ formId }: InternalFormContextValue) =>
|
193
|
-
useFormStore(formId, (state) => state.setTouched);
|
194
|
-
|
195
|
-
export const useTouchedFields = (formId: InternalFormId) =>
|
196
|
-
useFormStore(formId, (state) => state.touchedFields);
|
197
|
-
|
198
|
-
export const useFieldErrors = (formId: InternalFormId) =>
|
199
|
-
useFormStore(formId, (state) => state.fieldErrors);
|
200
|
-
|
201
|
-
export const useSetFieldErrors = (formId: InternalFormId) =>
|
202
|
-
useFormStore(formId, (state) => state.setFieldErrors);
|
203
|
-
|
204
|
-
export const useResetFormElement = (formId: InternalFormId) =>
|
205
|
-
useFormStore(formId, (state) => state.resetFormElement);
|
206
|
-
|
207
|
-
export const useSubmitForm = (formId: InternalFormId) =>
|
208
|
-
useFormStore(formId, (state) => state.submit);
|
209
|
-
|
210
|
-
export const useFormActionProp = (formId: InternalFormId) =>
|
211
|
-
useFormStore(formId, (state) => state.formProps?.action);
|
212
|
-
|
213
|
-
export const useFormSubactionProp = (formId: InternalFormId) =>
|
214
|
-
useFormStore(formId, (state) => state.formProps?.subaction);
|
215
|
-
|
216
|
-
export const useFormValues = (formId: InternalFormId) =>
|
217
|
-
useFormStore(formId, (state) => state.getValues);
|
@@ -1,28 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* The purpose of this type is to simplify the logic
|
3
|
-
* around data that needs to come from the server initially,
|
4
|
-
* but from the internal state after hydration.
|
5
|
-
*/
|
6
|
-
export type Hydratable<T> = {
|
7
|
-
hydrateTo: (data: T) => T;
|
8
|
-
map: <U>(fn: (data: T) => U) => Hydratable<U>;
|
9
|
-
};
|
10
|
-
|
11
|
-
const serverData = <T>(data: T): Hydratable<T> => ({
|
12
|
-
hydrateTo: () => data,
|
13
|
-
map: (fn) => serverData(fn(data)),
|
14
|
-
});
|
15
|
-
|
16
|
-
const hydratedData = <T>(): Hydratable<T> => ({
|
17
|
-
hydrateTo: (hydratedData: T) => hydratedData,
|
18
|
-
map: <U>() => hydratedData<U>(),
|
19
|
-
});
|
20
|
-
|
21
|
-
const from = <T>(data: T, hydrated: boolean): Hydratable<T> =>
|
22
|
-
hydrated ? hydratedData<T>() : serverData<T>(data);
|
23
|
-
|
24
|
-
export const hydratable = {
|
25
|
-
serverData,
|
26
|
-
hydratedData,
|
27
|
-
from,
|
28
|
-
};
|
@@ -1,10 +0,0 @@
|
|
1
|
-
export const getCheckboxChecked = (
|
2
|
-
checkboxValue: string | undefined = "on",
|
3
|
-
newValue: unknown
|
4
|
-
): boolean | undefined => {
|
5
|
-
if (Array.isArray(newValue))
|
6
|
-
return newValue.some((val) => val === true || val === checkboxValue);
|
7
|
-
if (typeof newValue === "boolean") return newValue;
|
8
|
-
if (typeof newValue === "string") return newValue === checkboxValue;
|
9
|
-
return undefined;
|
10
|
-
};
|
@@ -1,18 +0,0 @@
|
|
1
|
-
export const getRadioChecked = (
|
2
|
-
radioValue: string | undefined = "on",
|
3
|
-
newValue: unknown
|
4
|
-
) => {
|
5
|
-
if (typeof newValue === "string") return newValue === radioValue;
|
6
|
-
return undefined;
|
7
|
-
};
|
8
|
-
|
9
|
-
if (import.meta.vitest) {
|
10
|
-
const { it, expect } = import.meta.vitest;
|
11
|
-
it("getRadioChecked", () => {
|
12
|
-
expect(getRadioChecked("on", "on")).toBe(true);
|
13
|
-
expect(getRadioChecked("on", undefined)).toBe(undefined);
|
14
|
-
expect(getRadioChecked("trueValue", undefined)).toBe(undefined);
|
15
|
-
expect(getRadioChecked("trueValue", "bob")).toBe(false);
|
16
|
-
expect(getRadioChecked("trueValue", "trueValue")).toBe(true);
|
17
|
-
});
|
18
|
-
}
|
@@ -1,63 +0,0 @@
|
|
1
|
-
export const nestedObjectToPathObject = (
|
2
|
-
val: any,
|
3
|
-
acc: Record<string, any>,
|
4
|
-
path: string
|
5
|
-
): any => {
|
6
|
-
if (Array.isArray(val)) {
|
7
|
-
val.forEach((v, index) =>
|
8
|
-
nestedObjectToPathObject(v, acc, `${path}[${index}]`)
|
9
|
-
);
|
10
|
-
return acc;
|
11
|
-
}
|
12
|
-
|
13
|
-
if (typeof val === "object") {
|
14
|
-
Object.entries(val).forEach(([key, value]) => {
|
15
|
-
const nextPath = path ? `${path}.${key}` : key;
|
16
|
-
nestedObjectToPathObject(value, acc, nextPath);
|
17
|
-
});
|
18
|
-
return acc;
|
19
|
-
}
|
20
|
-
|
21
|
-
if (val !== undefined) {
|
22
|
-
acc[path] = val;
|
23
|
-
}
|
24
|
-
|
25
|
-
return acc;
|
26
|
-
};
|
27
|
-
|
28
|
-
if (import.meta.vitest) {
|
29
|
-
const { describe, expect, it } = import.meta.vitest;
|
30
|
-
|
31
|
-
describe("nestedObjectToPathObject", () => {
|
32
|
-
it("should return an object with the correct path", () => {
|
33
|
-
const result = nestedObjectToPathObject(
|
34
|
-
{
|
35
|
-
a: 1,
|
36
|
-
b: 2,
|
37
|
-
c: { foo: "bar", baz: [true, false] },
|
38
|
-
d: [
|
39
|
-
{ foo: "bar", baz: [true, false] },
|
40
|
-
{ e: true, f: "hi" },
|
41
|
-
],
|
42
|
-
g: undefined,
|
43
|
-
},
|
44
|
-
{},
|
45
|
-
""
|
46
|
-
);
|
47
|
-
|
48
|
-
expect(result).toEqual({
|
49
|
-
a: 1,
|
50
|
-
b: 2,
|
51
|
-
"c.foo": "bar",
|
52
|
-
"c.baz[0]": true,
|
53
|
-
"c.baz[1]": false,
|
54
|
-
"d[0].foo": "bar",
|
55
|
-
"d[0].baz[0]": true,
|
56
|
-
"d[0].baz[1]": false,
|
57
|
-
"d[1].e": true,
|
58
|
-
"d[1].f": "hi",
|
59
|
-
});
|
60
|
-
expect(Object.keys(result)).toHaveLength(10);
|
61
|
-
});
|
62
|
-
});
|
63
|
-
}
|