remix-validated-form 5.0.2 → 5.1.1-beta.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. package/.turbo/turbo-build.log +152 -8
  2. package/dist/index.cjs.js +898 -63
  3. package/dist/index.cjs.js.map +1 -1
  4. package/dist/index.d.ts +7 -2
  5. package/dist/index.esm.js +876 -15
  6. package/dist/index.esm.js.map +1 -1
  7. package/package.json +4 -4
  8. package/src/ValidatedForm.tsx +0 -427
  9. package/src/hooks.ts +0 -160
  10. package/src/index.ts +0 -12
  11. package/src/internal/MultiValueMap.ts +0 -44
  12. package/src/internal/constants.ts +0 -4
  13. package/src/internal/flatten.ts +0 -12
  14. package/src/internal/formContext.ts +0 -13
  15. package/src/internal/getInputProps.test.ts +0 -251
  16. package/src/internal/getInputProps.ts +0 -94
  17. package/src/internal/hooks.ts +0 -217
  18. package/src/internal/hydratable.ts +0 -28
  19. package/src/internal/logic/getCheckboxChecked.ts +0 -10
  20. package/src/internal/logic/getRadioChecked.ts +0 -18
  21. package/src/internal/logic/nestedObjectToPathObject.ts +0 -63
  22. package/src/internal/logic/requestSubmit.test.tsx +0 -24
  23. package/src/internal/logic/requestSubmit.ts +0 -103
  24. package/src/internal/state/arrayUtil.ts +0 -451
  25. package/src/internal/state/controlledFields.ts +0 -86
  26. package/src/internal/state/createFormStore.ts +0 -591
  27. package/src/internal/state/fieldArray.tsx +0 -197
  28. package/src/internal/state/storeHooks.ts +0 -9
  29. package/src/internal/state/types.ts +0 -1
  30. package/src/internal/submissionCallbacks.ts +0 -15
  31. package/src/internal/util.ts +0 -39
  32. package/src/server.ts +0 -53
  33. package/src/unreleased/formStateHooks.ts +0 -170
  34. package/src/userFacingFormContext.ts +0 -147
  35. package/src/validation/createValidator.ts +0 -53
  36. package/src/validation/types.ts +0 -72
  37. package/tsconfig.json +0 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remix-validated-form",
3
- "version": "5.0.2",
3
+ "version": "5.1.1-beta.0",
4
4
  "description": "Form component and utils for easy form validation in remix",
5
5
  "main": "./dist/index.cjs.js",
6
6
  "module": "./dist/index.esm.js",
@@ -45,6 +45,8 @@
45
45
  "@types/lodash.get": "^4.4.7",
46
46
  "@types/react": "^18.0.9",
47
47
  "fetch-blob": "^3.1.3",
48
+ "immer": "^9.0.12",
49
+ "zustand": "^4.3.0",
48
50
  "react": "^18.1.0",
49
51
  "set-get": "*",
50
52
  "ts-toolbelt": "^9.6.0",
@@ -53,11 +55,9 @@
53
55
  "typescript": "^4.8.4"
54
56
  },
55
57
  "dependencies": {
56
- "immer": "^9.0.12",
57
58
  "lodash.get": "^4.4.2",
58
59
  "nanoid": "3.3.6",
59
60
  "remeda": "^1.2.0",
60
- "tiny-invariant": "^1.2.0",
61
- "zustand": "^4.3.0"
61
+ "tiny-invariant": "^1.2.0"
62
62
  }
63
63
  }
@@ -1,427 +0,0 @@
1
- import {
2
- FetcherWithComponents,
3
- Form as RemixForm,
4
- FormMethod,
5
- useSubmit,
6
- SubmitOptions,
7
- FormEncType,
8
- } from "@remix-run/react";
9
- import React, {
10
- ComponentProps,
11
- FormEvent,
12
- RefObject,
13
- useCallback,
14
- useEffect,
15
- useMemo,
16
- useRef,
17
- useState,
18
- } from "react";
19
- import * as R from "remeda";
20
- import { useIsSubmitting, useIsValid } from "./hooks";
21
- import { FORM_ID_FIELD } from "./internal/constants";
22
- import {
23
- InternalFormContext,
24
- InternalFormContextValue,
25
- } from "./internal/formContext";
26
- import {
27
- useDefaultValuesFromLoader,
28
- useErrorResponseForForm,
29
- useHasActiveFormSubmit,
30
- useSetFieldErrors,
31
- } from "./internal/hooks";
32
- import { MultiValueMap, useMultiValueMap } from "./internal/MultiValueMap";
33
- import {
34
- SyncedFormProps,
35
- useRootFormStore,
36
- } from "./internal/state/createFormStore";
37
- import { useFormStore } from "./internal/state/storeHooks";
38
- import { useSubmitComplete } from "./internal/submissionCallbacks";
39
- import {
40
- mergeRefs,
41
- useDeepEqualsMemo,
42
- useIsomorphicLayoutEffect as useLayoutEffect,
43
- } from "./internal/util";
44
- import { FieldErrors, Validator } from "./validation/types";
45
-
46
- type SubactionData<
47
- DataType,
48
- Subaction extends string | undefined
49
- > = DataType & { subaction: Subaction };
50
-
51
- // Not all validation libraries support encoding a literal value in the schema type (e.g. yup).
52
- // This condition here allows us to provide strictness for users who are using a validation library that does support it,
53
- // but also allows us to support users who are using a validation library that doesn't support it.
54
- type DataForSubaction<
55
- DataType,
56
- Subaction extends string | undefined
57
- > = Subaction extends string // Not all validation libraries support encoding a literal value in the schema type.
58
- ? SubactionData<DataType, Subaction> extends undefined
59
- ? DataType
60
- : SubactionData<DataType, Subaction>
61
- : DataType;
62
-
63
- export type FormProps<DataType, Subaction extends string | undefined> = {
64
- /**
65
- * A `Validator` object that describes how to validate the form.
66
- */
67
- validator: Validator<DataType>;
68
- /**
69
- * A submit callback that gets called when the form is submitted
70
- * after all validations have been run.
71
- */
72
- onSubmit?: (
73
- data: DataForSubaction<DataType, Subaction>,
74
- event: React.FormEvent<HTMLFormElement>
75
- ) => void | Promise<void>;
76
- /**
77
- * Allows you to provide a `fetcher` from Remix's `useFetcher` hook.
78
- * The form will use the fetcher for loading states, action data, etc
79
- * instead of the default form action.
80
- */
81
- fetcher?: FetcherWithComponents<any>;
82
- /**
83
- * Accepts an object of default values for the form
84
- * that will automatically be propagated to the form fields via `useField`.
85
- */
86
- defaultValues?: Partial<DataForSubaction<DataType, Subaction>>;
87
- /**
88
- * A ref to the form element.
89
- */
90
- formRef?: React.RefObject<HTMLFormElement>;
91
- /**
92
- * An optional sub-action to use for the form.
93
- * Setting a value here will cause the form to be submitted with an extra `subaction` value.
94
- * This can be useful when there are multiple forms on the screen handled by the same action.
95
- */
96
- subaction?: Subaction;
97
- /**
98
- * Reset the form to the default values after the form has been successfully submitted.
99
- * This is useful if you want to submit the same form multiple times,
100
- * and don't redirect in-between submissions.
101
- */
102
- resetAfterSubmit?: boolean;
103
- /**
104
- * Normally, the first invalid input will be focused when the validation fails on form submit.
105
- * Set this to `false` to disable this behavior.
106
- */
107
- disableFocusOnError?: boolean;
108
- } & Omit<ComponentProps<typeof RemixForm>, "onSubmit">;
109
-
110
- const getDataFromForm = (el: HTMLFormElement) => new FormData(el);
111
-
112
- function nonNull<T>(value: T | null | undefined): value is T {
113
- return value !== null;
114
- }
115
-
116
- const focusFirstInvalidInput = (
117
- fieldErrors: FieldErrors,
118
- customFocusHandlers: MultiValueMap<string, () => void>,
119
- formElement: HTMLFormElement
120
- ) => {
121
- const namesInOrder = [...formElement.elements]
122
- .map((el) => {
123
- const input = el instanceof RadioNodeList ? el[0] : el;
124
- if (input instanceof HTMLElement && "name" in input)
125
- return (input as any).name;
126
- return null;
127
- })
128
- .filter(nonNull)
129
- .filter((name) => name in fieldErrors);
130
- const uniqueNamesInOrder = R.uniq(namesInOrder);
131
-
132
- for (const fieldName of uniqueNamesInOrder) {
133
- if (customFocusHandlers.has(fieldName)) {
134
- customFocusHandlers.getAll(fieldName).forEach((handler) => {
135
- handler();
136
- });
137
- break;
138
- }
139
-
140
- const elem = formElement.elements.namedItem(fieldName);
141
- if (!elem) continue;
142
-
143
- if (elem instanceof RadioNodeList) {
144
- const selectedRadio =
145
- [...elem]
146
- .filter(
147
- (item): item is HTMLInputElement => item instanceof HTMLInputElement
148
- )
149
- .find((item) => item.value === elem.value) ?? elem[0];
150
- if (selectedRadio && selectedRadio instanceof HTMLInputElement) {
151
- selectedRadio.focus();
152
- break;
153
- }
154
- }
155
-
156
- if (elem instanceof HTMLElement) {
157
- if (elem instanceof HTMLInputElement && elem.type === "hidden") {
158
- continue;
159
- }
160
-
161
- elem.focus();
162
- break;
163
- }
164
- }
165
- };
166
-
167
- const useFormId = (providedId?: string): string | symbol => {
168
- // We can use a `Symbol` here because we only use it after hydration
169
- const [symbolId] = useState(() => Symbol("remix-validated-form-id"));
170
- return providedId ?? symbolId;
171
- };
172
-
173
- /**
174
- * Use a component to access the state so we don't cause
175
- * any extra rerenders of the whole form.
176
- */
177
- const FormResetter = ({
178
- resetAfterSubmit,
179
- formRef,
180
- }: {
181
- resetAfterSubmit: boolean;
182
- formRef: RefObject<HTMLFormElement>;
183
- }) => {
184
- const isSubmitting = useIsSubmitting();
185
- const isValid = useIsValid();
186
- useSubmitComplete(isSubmitting, () => {
187
- if (isValid && resetAfterSubmit) {
188
- formRef.current?.reset();
189
- }
190
- });
191
- return null;
192
- };
193
-
194
- function formEventProxy<T extends object>(event: T): T {
195
- let defaultPrevented = false;
196
- return new Proxy(event, {
197
- get: (target, prop) => {
198
- if (prop === "preventDefault") {
199
- return () => {
200
- defaultPrevented = true;
201
- };
202
- }
203
-
204
- if (prop === "defaultPrevented") {
205
- return defaultPrevented;
206
- }
207
-
208
- return target[prop as keyof T];
209
- },
210
- }) as T;
211
- }
212
-
213
- type HTMLSubmitEvent = React.BaseSyntheticEvent<
214
- SubmitEvent,
215
- Event,
216
- HTMLFormElement
217
- >;
218
-
219
- type HTMLFormSubmitter = HTMLButtonElement | HTMLInputElement;
220
-
221
- /**
222
- * The primary form component of `remix-validated-form`.
223
- */
224
- export function ValidatedForm<DataType, Subaction extends string | undefined>({
225
- validator,
226
- onSubmit,
227
- children,
228
- fetcher,
229
- action,
230
- defaultValues: unMemoizedDefaults,
231
- formRef: formRefProp,
232
- onReset,
233
- subaction,
234
- resetAfterSubmit = false,
235
- disableFocusOnError,
236
- method,
237
- replace,
238
- id,
239
- preventScrollReset,
240
- relative,
241
- encType,
242
- ...rest
243
- }: FormProps<DataType, Subaction>) {
244
- const formId = useFormId(id);
245
- const providedDefaultValues = useDeepEqualsMemo(unMemoizedDefaults);
246
- const contextValue = useMemo<InternalFormContextValue>(
247
- () => ({
248
- formId,
249
- action,
250
- subaction,
251
- defaultValuesProp: providedDefaultValues,
252
- fetcher,
253
- }),
254
- [action, fetcher, formId, providedDefaultValues, subaction]
255
- );
256
- const backendError = useErrorResponseForForm(contextValue);
257
- const backendDefaultValues = useDefaultValuesFromLoader(contextValue);
258
- const hasActiveSubmission = useHasActiveFormSubmit(contextValue);
259
- const formRef = useRef<HTMLFormElement>(null);
260
- const Form = fetcher?.Form ?? RemixForm;
261
-
262
- const submit = useSubmit();
263
- const setFieldErrors = useSetFieldErrors(formId);
264
- const setFieldError = useFormStore(formId, (state) => state.setFieldError);
265
- const reset = useFormStore(formId, (state) => state.reset);
266
- const startSubmit = useFormStore(formId, (state) => state.startSubmit);
267
- const endSubmit = useFormStore(formId, (state) => state.endSubmit);
268
- const syncFormProps = useFormStore(formId, (state) => state.syncFormProps);
269
- const setFormElementInState = useFormStore(
270
- formId,
271
- (state) => state.setFormElement
272
- );
273
- const cleanupForm = useRootFormStore((state) => state.cleanupForm);
274
- const registerForm = useRootFormStore((state) => state.registerForm);
275
-
276
- const customFocusHandlers = useMultiValueMap<string, () => void>();
277
- const registerReceiveFocus: SyncedFormProps["registerReceiveFocus"] =
278
- useCallback(
279
- (fieldName, handler) => {
280
- customFocusHandlers().add(fieldName, handler);
281
- return () => {
282
- customFocusHandlers().remove(fieldName, handler);
283
- };
284
- },
285
- [customFocusHandlers]
286
- );
287
-
288
- // TODO: all these hooks running at startup cause extra, unnecessary renders
289
- // There must be a nice way to avoid this.
290
- useLayoutEffect(() => {
291
- registerForm(formId);
292
- return () => cleanupForm(formId);
293
- }, [cleanupForm, formId, registerForm]);
294
-
295
- useLayoutEffect(() => {
296
- syncFormProps({
297
- action,
298
- defaultValues: providedDefaultValues ?? backendDefaultValues ?? {},
299
- subaction,
300
- registerReceiveFocus,
301
- validator,
302
- });
303
- }, [
304
- action,
305
- providedDefaultValues,
306
- registerReceiveFocus,
307
- subaction,
308
- syncFormProps,
309
- backendDefaultValues,
310
- validator,
311
- ]);
312
-
313
- useLayoutEffect(() => {
314
- setFormElementInState(formRef.current);
315
- }, [setFormElementInState]);
316
-
317
- useEffect(() => {
318
- setFieldErrors(backendError?.fieldErrors ?? {});
319
- if (!disableFocusOnError && backendError?.fieldErrors) {
320
- focusFirstInvalidInput(
321
- backendError.fieldErrors,
322
- customFocusHandlers(),
323
- formRef.current!
324
- );
325
- }
326
- }, [
327
- backendError?.fieldErrors,
328
- customFocusHandlers,
329
- disableFocusOnError,
330
- setFieldErrors,
331
- setFieldError,
332
- ]);
333
-
334
- useSubmitComplete(hasActiveSubmission, () => {
335
- endSubmit();
336
- });
337
-
338
- const handleSubmit = async (
339
- e: FormEvent<HTMLFormElement>,
340
- target: typeof e.currentTarget,
341
- nativeEvent: HTMLSubmitEvent["nativeEvent"]
342
- ) => {
343
- startSubmit();
344
- const submitter = nativeEvent.submitter as HTMLFormSubmitter | null;
345
- const formMethod = (submitter?.formMethod as FormMethod) || method;
346
- const formData = getDataFromForm(target);
347
- if (submitter?.name) {
348
- formData.append(submitter.name, submitter.value);
349
- }
350
-
351
- const result = await validator.validate(formData);
352
- if (result.error) {
353
- setFieldErrors(result.error.fieldErrors);
354
- endSubmit();
355
- if (!disableFocusOnError) {
356
- focusFirstInvalidInput(
357
- result.error.fieldErrors,
358
- customFocusHandlers(),
359
- formRef.current!
360
- );
361
- }
362
- } else {
363
- setFieldErrors({});
364
- const eventProxy = formEventProxy(e);
365
- await onSubmit?.(result.data as any, eventProxy);
366
- if (eventProxy.defaultPrevented) {
367
- endSubmit();
368
- return;
369
- }
370
-
371
- const opts: SubmitOptions = {
372
- method: formMethod,
373
- replace,
374
- preventScrollReset,
375
- relative,
376
- action,
377
- encType: encType as FormEncType | undefined,
378
- };
379
-
380
- // We deviate from the Remix code here a bit because of our async submit.
381
- // In Remix's `FormImpl`, they use `event.currentTarget` to get the form,
382
- // but we already have the form in `formRef.current` so we can just use that.
383
- // If we use `event.currentTarget` here, it will break because `currentTarget`
384
- // will have changed since the start of the submission.
385
- if (fetcher) fetcher.submit(formData, opts);
386
- else submit(formData, opts);
387
- }
388
- };
389
-
390
- return (
391
- <Form
392
- ref={mergeRefs([formRef, formRefProp])}
393
- {...rest}
394
- id={id}
395
- action={action}
396
- method={method}
397
- encType={encType}
398
- replace={replace}
399
- preventScrollReset={preventScrollReset}
400
- relative={relative}
401
- onSubmit={(e) => {
402
- e.preventDefault();
403
- handleSubmit(
404
- e,
405
- e.currentTarget,
406
- (e as unknown as HTMLSubmitEvent).nativeEvent
407
- );
408
- }}
409
- onReset={(event) => {
410
- onReset?.(event);
411
- if (event.defaultPrevented) return;
412
- reset();
413
- }}
414
- >
415
- <InternalFormContext.Provider value={contextValue}>
416
- <>
417
- <FormResetter formRef={formRef} resetAfterSubmit={resetAfterSubmit} />
418
- {subaction && (
419
- <input type="hidden" value={subaction} name="subaction" />
420
- )}
421
- {id && <input type="hidden" value={id} name={FORM_ID_FIELD} />}
422
- {children}
423
- </>
424
- </InternalFormContext.Provider>
425
- </Form>
426
- );
427
- }
package/src/hooks.ts DELETED
@@ -1,160 +0,0 @@
1
- import { useEffect, useMemo } from "react";
2
- import {
3
- createGetInputProps,
4
- GetInputProps,
5
- ValidationBehaviorOptions,
6
- } from "./internal/getInputProps";
7
- import {
8
- useInternalFormContext,
9
- useFieldTouched,
10
- useFieldError,
11
- useFieldDefaultValue,
12
- useClearError,
13
- useInternalIsSubmitting,
14
- useInternalIsValid,
15
- useInternalHasBeenSubmitted,
16
- useRegisterReceiveFocus,
17
- useSmartValidate,
18
- } from "./internal/hooks";
19
- import {
20
- useControllableValue,
21
- useUpdateControllableValue,
22
- } from "./internal/state/controlledFields";
23
-
24
- /**
25
- * Returns whether or not the parent form is currently being submitted.
26
- * This is different from Remix's `useNavigation()` in that it
27
- * is aware of what form it's in and when _that_ form is being submitted.
28
- *
29
- * @param formId
30
- */
31
- export const useIsSubmitting = (formId?: string) => {
32
- const formContext = useInternalFormContext(formId, "useIsSubmitting");
33
- return useInternalIsSubmitting(formContext.formId);
34
- };
35
-
36
- /**
37
- * Returns whether or not the current form is valid.
38
- *
39
- * @param formId the id of the form. Only necessary if being used outside a ValidatedForm.
40
- */
41
- export const useIsValid = (formId?: string) => {
42
- const formContext = useInternalFormContext(formId, "useIsValid");
43
- return useInternalIsValid(formContext.formId);
44
- };
45
-
46
- export type FieldProps = {
47
- /**
48
- * The validation error message if there is one.
49
- */
50
- error?: string;
51
- /**
52
- * Clears the error message.
53
- */
54
- clearError: () => void;
55
- /**
56
- * Validates the field.
57
- */
58
- validate: () => void;
59
- /**
60
- * The default value of the field, if there is one.
61
- */
62
- defaultValue?: any;
63
- /**
64
- * Whether or not the field has been touched.
65
- */
66
- touched: boolean;
67
- /**
68
- * Helper to set the touched state of the field.
69
- */
70
- setTouched: (touched: boolean) => void;
71
- /**
72
- * Helper to get all the props necessary for a regular input.
73
- */
74
- getInputProps: GetInputProps;
75
- };
76
-
77
- /**
78
- * Provides the data and helpers necessary to set up a field.
79
- */
80
- export const useField = (
81
- name: string,
82
- options?: {
83
- /**
84
- * Allows you to configure a custom function that will be called
85
- * when the input needs to receive focus due to a validation error.
86
- * This is useful for custom components that use a hidden input.
87
- */
88
- handleReceiveFocus?: () => void;
89
- /**
90
- * Allows you to specify when a field gets validated (when using getInputProps)
91
- */
92
- validationBehavior?: Partial<ValidationBehaviorOptions>;
93
- /**
94
- * The formId of the form you want to use.
95
- * This is not necesary if the input is used inside a form.
96
- */
97
- formId?: string;
98
- }
99
- ): FieldProps => {
100
- const { formId: providedFormId, handleReceiveFocus } = options ?? {};
101
- const formContext = useInternalFormContext(providedFormId, "useField");
102
-
103
- const defaultValue = useFieldDefaultValue(name, formContext);
104
- const [touched, setTouched] = useFieldTouched(name, formContext);
105
- const error = useFieldError(name, formContext);
106
- const clearError = useClearError(formContext);
107
-
108
- const hasBeenSubmitted = useInternalHasBeenSubmitted(formContext.formId);
109
- const smartValidate = useSmartValidate(formContext.formId);
110
- const registerReceiveFocus = useRegisterReceiveFocus(formContext.formId);
111
-
112
- useEffect(() => {
113
- if (handleReceiveFocus)
114
- return registerReceiveFocus(name, handleReceiveFocus);
115
- }, [handleReceiveFocus, name, registerReceiveFocus]);
116
-
117
- const field = useMemo<FieldProps>(() => {
118
- const helpers = {
119
- error,
120
- clearError: () => clearError(name),
121
- validate: () => smartValidate({ alwaysIncludeErrorsFromFields: [name] }),
122
- defaultValue,
123
- touched,
124
- setTouched,
125
- };
126
- const getInputProps = createGetInputProps({
127
- ...helpers,
128
- name,
129
- hasBeenSubmitted,
130
- validationBehavior: options?.validationBehavior,
131
- });
132
- return {
133
- ...helpers,
134
- getInputProps,
135
- };
136
- }, [
137
- error,
138
- clearError,
139
- defaultValue,
140
- touched,
141
- setTouched,
142
- name,
143
- hasBeenSubmitted,
144
- options?.validationBehavior,
145
- smartValidate,
146
- ]);
147
-
148
- return field;
149
- };
150
-
151
- export const useControlField = <T>(name: string, formId?: string) => {
152
- const context = useInternalFormContext(formId, "useControlField");
153
- const [value, setValue] = useControllableValue(context, name);
154
- return [value as T, setValue as (value: T) => void] as const;
155
- };
156
-
157
- export const useUpdateControlledField = (formId?: string) => {
158
- const context = useInternalFormContext(formId, "useControlField");
159
- return useUpdateControllableValue(context.formId);
160
- };
package/src/index.ts DELETED
@@ -1,12 +0,0 @@
1
- export * from "./hooks";
2
- export * from "./server";
3
- export * from "./ValidatedForm";
4
- export * from "./validation/types";
5
- export * from "./validation/createValidator";
6
- export * from "./userFacingFormContext";
7
- export {
8
- FieldArray,
9
- useFieldArray,
10
- type FieldArrayProps,
11
- type FieldArrayHelpers,
12
- } from "./internal/state/fieldArray";
@@ -1,44 +0,0 @@
1
- import { useCallback, useRef } from "react";
2
-
3
- export class MultiValueMap<Key, Value> {
4
- private dict: Map<Key, Value[]> = new Map();
5
-
6
- add = (key: Key, value: Value) => {
7
- if (this.dict.has(key)) {
8
- this.dict.get(key)!.push(value);
9
- } else {
10
- this.dict.set(key, [value]);
11
- }
12
- };
13
-
14
- delete = (key: Key) => {
15
- this.dict.delete(key);
16
- };
17
-
18
- remove = (key: Key, value: Value) => {
19
- if (!this.dict.has(key)) return;
20
- const array = this.dict.get(key)!;
21
- const index = array.indexOf(value);
22
- if (index !== -1) array.splice(index, 1);
23
- if (array.length === 0) this.dict.delete(key);
24
- };
25
-
26
- getAll = (key: Key): Value[] => {
27
- return this.dict.get(key) ?? [];
28
- };
29
-
30
- entries = (): IterableIterator<[Key, Value[]]> => this.dict.entries();
31
-
32
- values = (): IterableIterator<Value[]> => this.dict.values();
33
-
34
- has = (key: Key): boolean => this.dict.has(key);
35
- }
36
-
37
- export const useMultiValueMap = <Key, Value>() => {
38
- const ref = useRef<MultiValueMap<Key, Value> | null>(null);
39
- return useCallback(() => {
40
- if (ref.current) return ref.current;
41
- ref.current = new MultiValueMap();
42
- return ref.current;
43
- }, []);
44
- };
@@ -1,4 +0,0 @@
1
- export const FORM_ID_FIELD = "__rvfInternalFormId" as const;
2
- export const FORM_DEFAULTS_FIELD = "__rvfInternalFormDefaults" as const;
3
- export const formDefaultValuesKey = (formId: string) =>
4
- `${FORM_DEFAULTS_FIELD}_${formId}`;
@@ -1,12 +0,0 @@
1
- import { setPath } from "set-get";
2
- import { MultiValueMap } from "./MultiValueMap";
3
-
4
- export const objectFromPathEntries = (entries: [string, any][]) => {
5
- const map = new MultiValueMap<string, any>();
6
- entries.forEach(([key, value]) => map.add(key, value));
7
- return [...map.entries()].reduce(
8
- (acc, [key, value]) =>
9
- setPath(acc, key, value.length === 1 ? value[0] : value),
10
- {} as Record<string, any>
11
- );
12
- };
@@ -1,13 +0,0 @@
1
- import { FetcherWithComponents } from "@remix-run/react";
2
- import { createContext } from "react";
3
-
4
- export type InternalFormContextValue = {
5
- formId: string | symbol;
6
- action?: string;
7
- subaction?: string;
8
- defaultValuesProp?: { [fieldName: string]: any };
9
- fetcher?: FetcherWithComponents<unknown>;
10
- };
11
-
12
- export const InternalFormContext =
13
- createContext<InternalFormContextValue | null>(null);