remix-validated-form 4.4.1 → 4.5.0-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 +8 -5
- package/browser/ValidatedForm.js +21 -15
- package/browser/hooks.d.ts +1 -1
- package/browser/internal/hooks.d.ts +1 -1
- package/browser/internal/state/controlledFieldStore.d.ts +23 -21
- package/browser/internal/state/controlledFieldStore.js +32 -19
- package/browser/internal/state/controlledFields.d.ts +3 -3
- package/browser/internal/state/controlledFields.js +19 -21
- package/browser/internal/state/createFormStore.d.ts +13 -6
- package/browser/internal/state/createFormStore.js +48 -5
- package/browser/internal/state/storeHooks.d.ts +1 -3
- package/browser/internal/state/storeHooks.js +2 -8
- package/browser/internal/state/types.d.ts +1 -0
- package/browser/internal/state/types.js +1 -0
- package/browser/userFacingFormContext.d.ts +7 -0
- package/browser/userFacingFormContext.js +14 -4
- package/dist/remix-validated-form.cjs.js +4 -3
- package/dist/remix-validated-form.cjs.js.map +1 -0
- package/dist/remix-validated-form.es.js +136 -86
- package/dist/remix-validated-form.es.js.map +1 -0
- package/dist/remix-validated-form.umd.js +4 -3
- package/dist/remix-validated-form.umd.js.map +1 -0
- package/dist/types/hooks.d.ts +1 -1
- package/dist/types/internal/hooks.d.ts +1 -1
- package/dist/types/internal/state/controlledFieldStore.d.ts +23 -21
- package/dist/types/internal/state/controlledFields.d.ts +3 -3
- package/dist/types/internal/state/createFormStore.d.ts +13 -6
- package/dist/types/internal/state/storeHooks.d.ts +1 -3
- package/dist/types/internal/state/types.d.ts +1 -0
- package/package.json +4 -4
- package/src/ValidatedForm.tsx +34 -23
- package/src/internal/hooks.ts +1 -1
- package/src/internal/state/controlledFieldStore.ts +95 -74
- package/src/internal/state/controlledFields.ts +38 -26
- package/src/internal/state/createFormStore.ts +174 -113
- package/src/internal/state/storeHooks.ts +3 -16
- package/src/internal/state/types.ts +1 -0
- package/src/userFacingFormContext.ts +23 -11
- package/dist/types/internal/state/cleanup.d.ts +0 -2
- package/dist/types/internal/state/storeFamily.d.ts +0 -9
- package/src/internal/state/cleanup.ts +0 -8
- package/src/internal/state/storeFamily.ts +0 -24
package/dist/types/hooks.d.ts
CHANGED
@@ -64,4 +64,4 @@ export declare const useField: (name: string, options?: {
|
|
64
64
|
formId?: string | undefined;
|
65
65
|
} | undefined) => FieldProps;
|
66
66
|
export declare const useControlField: <T>(name: string, formId?: string | undefined) => readonly [T, (value: T) => void];
|
67
|
-
export declare const useUpdateControlledField: (formId?: string | undefined) => (
|
67
|
+
export declare const useUpdateControlledField: (formId?: string | undefined) => (field: string, value: unknown) => void;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { FieldErrors, ValidationErrorResponseData } from "..";
|
2
2
|
import { InternalFormContextValue } from "./formContext";
|
3
3
|
import { Hydratable } from "./hydratable";
|
4
|
-
import { InternalFormId } from "./state/
|
4
|
+
import { InternalFormId } from "./state/types";
|
5
5
|
export declare const useInternalFormContext: (formId?: string | symbol | undefined, hookName?: string | undefined) => InternalFormContextValue;
|
6
6
|
export declare function useErrorResponseForForm({ fetcher, subaction, formId, }: InternalFormContextValue): ValidationErrorResponseData | null;
|
7
7
|
export declare const useFieldErrorsForForm: (context: InternalFormContextValue) => Hydratable<FieldErrors | undefined>;
|
@@ -1,24 +1,26 @@
|
|
1
|
+
import { InternalFormId } from "./types";
|
2
|
+
export declare type FieldState = {
|
3
|
+
refCount: number;
|
4
|
+
value: unknown;
|
5
|
+
defaultValue?: unknown;
|
6
|
+
hydrated: boolean;
|
7
|
+
valueUpdatePromise: Promise<void> | undefined;
|
8
|
+
resolveValueUpdate: (() => void) | undefined;
|
9
|
+
};
|
1
10
|
export declare type ControlledFieldState = {
|
2
|
-
|
3
|
-
[
|
4
|
-
|
5
|
-
|
6
|
-
defaultValue?: unknown;
|
7
|
-
hydrated: boolean;
|
8
|
-
valueUpdatePromise: Promise<void> | undefined;
|
9
|
-
resolveValueUpdate: (() => void) | undefined;
|
10
|
-
} | undefined;
|
11
|
+
forms: {
|
12
|
+
[formId: InternalFormId]: {
|
13
|
+
[fieldName: string]: FieldState | undefined;
|
14
|
+
};
|
11
15
|
};
|
12
|
-
register: (fieldName: string) => void;
|
13
|
-
unregister: (fieldName: string) => void;
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
export declare const controlledFieldStore: {
|
20
|
-
(formId: import("./storeFamily").InternalFormId): import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<ControlledFieldState>, "setState"> & {
|
21
|
-
setState(nextStateOrUpdater: ControlledFieldState | Partial<ControlledFieldState> | ((state: import("immer/dist/internal").WritableDraft<ControlledFieldState>) => void), shouldReplace?: boolean | undefined): void;
|
22
|
-
}>;
|
23
|
-
remove(formId: import("./storeFamily").InternalFormId): void;
|
16
|
+
register: (formId: InternalFormId, fieldName: string) => void;
|
17
|
+
unregister: (formId: InternalFormId, fieldName: string) => void;
|
18
|
+
getField: (formId: InternalFormId, fieldName: string) => FieldState | undefined;
|
19
|
+
setValue: (formId: InternalFormId, fieldName: string, value: unknown) => void;
|
20
|
+
hydrateWithDefault: (formId: InternalFormId, fieldName: string, defaultValue: unknown) => void;
|
21
|
+
awaitValueUpdate: (formId: InternalFormId, fieldName: string) => Promise<void>;
|
22
|
+
reset: (formId: InternalFormId) => void;
|
24
23
|
};
|
24
|
+
export declare const useControlledFieldStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<ControlledFieldState>, "setState"> & {
|
25
|
+
setState(nextStateOrUpdater: ControlledFieldState | Partial<ControlledFieldState> | ((state: import("immer/dist/internal").WritableDraft<ControlledFieldState>) => void), shouldReplace?: boolean | undefined): void;
|
26
|
+
}>;
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { InternalFormContextValue } from "../formContext";
|
2
|
-
import { InternalFormId } from "./
|
2
|
+
import { InternalFormId } from "./types";
|
3
3
|
export declare const useControlledFieldValue: (context: InternalFormContextValue, field: string) => any;
|
4
4
|
export declare const useControllableValue: (context: InternalFormContextValue, field: string) => readonly [any, (value: unknown) => void];
|
5
|
-
export declare const useUpdateControllableValue: (formId: InternalFormId) => (
|
6
|
-
export declare const useAwaitValue: (formId: InternalFormId) => (
|
5
|
+
export declare const useUpdateControllableValue: (formId: InternalFormId) => (field: string, value: unknown) => void;
|
6
|
+
export declare const useAwaitValue: (formId: InternalFormId) => (field: string) => Promise<void>;
|
@@ -1,4 +1,6 @@
|
|
1
|
+
import { WritableDraft } from "immer/dist/internal";
|
1
2
|
import { FieldErrors, TouchedFields, Validator } from "../../validation/types";
|
3
|
+
import { InternalFormId } from "./types";
|
2
4
|
export declare type SyncedFormProps = {
|
3
5
|
formId?: string;
|
4
6
|
action?: string;
|
@@ -9,6 +11,14 @@ export declare type SyncedFormProps = {
|
|
9
11
|
registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
|
10
12
|
validator: Validator<unknown>;
|
11
13
|
};
|
14
|
+
export declare type FormStoreState = {
|
15
|
+
forms: {
|
16
|
+
[formId: InternalFormId]: FormState;
|
17
|
+
};
|
18
|
+
form: (formId: InternalFormId) => FormState;
|
19
|
+
registerForm: (formId: InternalFormId) => void;
|
20
|
+
cleanupForm: (formId: InternalFormId) => void;
|
21
|
+
};
|
12
22
|
export declare type FormState = {
|
13
23
|
isHydrated: boolean;
|
14
24
|
isSubmitting: boolean;
|
@@ -32,9 +42,6 @@ export declare type FormState = {
|
|
32
42
|
validate: () => Promise<void>;
|
33
43
|
resetFormElement: () => void;
|
34
44
|
};
|
35
|
-
export declare const
|
36
|
-
(
|
37
|
-
|
38
|
-
}>;
|
39
|
-
remove(formId: import("./storeFamily").InternalFormId): void;
|
40
|
-
};
|
45
|
+
export declare const useRootFormStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<FormStoreState>, "setState"> & {
|
46
|
+
setState(nextStateOrUpdater: FormStoreState | Partial<FormStoreState> | ((state: WritableDraft<FormStoreState>) => void), shouldReplace?: boolean | undefined): void;
|
47
|
+
}>;
|
@@ -1,5 +1,3 @@
|
|
1
|
-
import { ControlledFieldState } from "./controlledFieldStore";
|
2
1
|
import { FormState } from "./createFormStore";
|
3
|
-
import { InternalFormId } from "./
|
2
|
+
import { InternalFormId } from "./types";
|
4
3
|
export declare const useFormStore: <T>(formId: InternalFormId, selector: (state: FormState) => T) => T;
|
5
|
-
export declare const useControlledFieldStore: <T>(formId: InternalFormId, selector: (state: ControlledFieldState) => T) => T;
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare type InternalFormId = string | symbol;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "remix-validated-form",
|
3
|
-
"version": "4.
|
3
|
+
"version": "4.5.0-beta.0",
|
4
4
|
"description": "Form component and utils for easy form validation in remix",
|
5
5
|
"browser": "./dist/remix-validated-form.cjs.js",
|
6
6
|
"main": "./dist/remix-validated-form.umd.js",
|
@@ -41,9 +41,9 @@
|
|
41
41
|
"@remix-run/react": "^1.2.1",
|
42
42
|
"@remix-run/server-runtime": "^1.2.1",
|
43
43
|
"@types/lodash": "^4.14.178",
|
44
|
-
"@types/react": "^
|
44
|
+
"@types/react": "^18.0.9",
|
45
45
|
"fetch-blob": "^3.1.3",
|
46
|
-
"react": "^
|
46
|
+
"react": "^18.1.0",
|
47
47
|
"tsconfig": "*",
|
48
48
|
"typescript": "^4.5.3",
|
49
49
|
"vite-config": "*"
|
@@ -52,6 +52,6 @@
|
|
52
52
|
"immer": "^9.0.12",
|
53
53
|
"lodash": "^4.17.21",
|
54
54
|
"tiny-invariant": "^1.2.0",
|
55
|
-
"zustand": "^4.0.0-rc.
|
55
|
+
"zustand": "^4.0.0-rc.1"
|
56
56
|
}
|
57
57
|
}
|
package/src/ValidatedForm.tsx
CHANGED
@@ -23,12 +23,12 @@ import {
|
|
23
23
|
useSetFieldErrors,
|
24
24
|
} from "./internal/hooks";
|
25
25
|
import { MultiValueMap, useMultiValueMap } from "./internal/MultiValueMap";
|
26
|
-
import {
|
27
|
-
import { SyncedFormProps } from "./internal/state/createFormStore";
|
26
|
+
import { useControlledFieldStore } from "./internal/state/controlledFieldStore";
|
28
27
|
import {
|
29
|
-
|
30
|
-
|
31
|
-
} from "./internal/state/
|
28
|
+
SyncedFormProps,
|
29
|
+
useRootFormStore,
|
30
|
+
} from "./internal/state/createFormStore";
|
31
|
+
import { useFormStore } from "./internal/state/storeHooks";
|
32
32
|
import { useSubmitComplete } from "./internal/submissionCallbacks";
|
33
33
|
import {
|
34
34
|
mergeRefs,
|
@@ -186,6 +186,14 @@ function formEventProxy<T extends object>(event: T): T {
|
|
186
186
|
}) as T;
|
187
187
|
}
|
188
188
|
|
189
|
+
type HTMLSubmitEvent = React.BaseSyntheticEvent<
|
190
|
+
SubmitEvent,
|
191
|
+
Event,
|
192
|
+
HTMLFormElement
|
193
|
+
>;
|
194
|
+
|
195
|
+
type HTMLFormSubmitter = HTMLButtonElement | HTMLInputElement;
|
196
|
+
|
189
197
|
/**
|
190
198
|
* The primary form component of `remix-validated-form`.
|
191
199
|
*/
|
@@ -228,23 +236,16 @@ export function ValidatedForm<DataType>({
|
|
228
236
|
const setFieldErrors = useSetFieldErrors(formId);
|
229
237
|
const setFieldError = useFormStore(formId, (state) => state.setFieldError);
|
230
238
|
const reset = useFormStore(formId, (state) => state.reset);
|
231
|
-
const resetControlledFields = useControlledFieldStore(
|
232
|
-
formId,
|
233
|
-
(state) => state.reset
|
234
|
-
);
|
239
|
+
const resetControlledFields = useControlledFieldStore((state) => state.reset);
|
235
240
|
const startSubmit = useFormStore(formId, (state) => state.startSubmit);
|
236
241
|
const endSubmit = useFormStore(formId, (state) => state.endSubmit);
|
237
242
|
const syncFormProps = useFormStore(formId, (state) => state.syncFormProps);
|
238
|
-
const setHydrated = useFormStore(formId, (state) => state.setHydrated);
|
239
243
|
const setFormElementInState = useFormStore(
|
240
244
|
formId,
|
241
245
|
(state) => state.setFormElement
|
242
246
|
);
|
243
|
-
|
244
|
-
|
245
|
-
setHydrated();
|
246
|
-
return () => cleanupFormState(formId);
|
247
|
-
}, [formId, setHydrated]);
|
247
|
+
const cleanupForm = useRootFormStore((state) => state.cleanupForm);
|
248
|
+
const registerForm = useRootFormStore((state) => state.registerForm);
|
248
249
|
|
249
250
|
const customFocusHandlers = useMultiValueMap<string, () => void>();
|
250
251
|
const registerReceiveFocus: SyncedFormProps["registerReceiveFocus"] =
|
@@ -258,6 +259,13 @@ export function ValidatedForm<DataType>({
|
|
258
259
|
[customFocusHandlers]
|
259
260
|
);
|
260
261
|
|
262
|
+
// TODO: all these hooks running at startup cause extra, unnecessary renders
|
263
|
+
// There must be a nice way to avoid this.
|
264
|
+
useLayoutEffect(() => {
|
265
|
+
registerForm(formId);
|
266
|
+
return () => cleanupForm(formId);
|
267
|
+
}, [cleanupForm, formId, registerForm]);
|
268
|
+
|
261
269
|
useLayoutEffect(() => {
|
262
270
|
syncFormProps({
|
263
271
|
action,
|
@@ -276,6 +284,10 @@ export function ValidatedForm<DataType>({
|
|
276
284
|
validator,
|
277
285
|
]);
|
278
286
|
|
287
|
+
useLayoutEffect(() => {
|
288
|
+
setFormElementInState(formRef.current);
|
289
|
+
}, [setFormElementInState]);
|
290
|
+
|
279
291
|
useEffect(() => {
|
280
292
|
setFieldErrors(backendError?.fieldErrors ?? {});
|
281
293
|
}, [backendError?.fieldErrors, setFieldErrors, setFieldError]);
|
@@ -331,17 +343,16 @@ export function ValidatedForm<DataType>({
|
|
331
343
|
return;
|
332
344
|
}
|
333
345
|
|
346
|
+
const submitter = (e as unknown as HTMLSubmitEvent).nativeEvent
|
347
|
+
.submitter as HTMLFormSubmitter | null;
|
348
|
+
|
334
349
|
// We deviate from the remix code here a bit because of our async submit.
|
335
350
|
// In remix's `FormImpl`, they use `event.currentTarget` to get the form,
|
336
351
|
// but we already have the form in `formRef.current` so we can just use that.
|
337
352
|
// If we use `event.currentTarget` here, it will break because `currentTarget`
|
338
353
|
// will have changed since the start of the submission.
|
339
|
-
if (fetcher) fetcher.submit(
|
340
|
-
else
|
341
|
-
submit(clickedButtonRef.current || formRef.current, {
|
342
|
-
method,
|
343
|
-
replace,
|
344
|
-
});
|
354
|
+
if (fetcher) fetcher.submit(submitter || e.currentTarget);
|
355
|
+
else submit(submitter || e.currentTarget, { method, replace });
|
345
356
|
|
346
357
|
clickedButtonRef.current = null;
|
347
358
|
}
|
@@ -349,7 +360,7 @@ export function ValidatedForm<DataType>({
|
|
349
360
|
|
350
361
|
return (
|
351
362
|
<Form
|
352
|
-
ref={mergeRefs([formRef, formRefProp
|
363
|
+
ref={mergeRefs([formRef, formRefProp])}
|
353
364
|
{...rest}
|
354
365
|
id={id}
|
355
366
|
action={action}
|
@@ -363,7 +374,7 @@ export function ValidatedForm<DataType>({
|
|
363
374
|
onReset?.(event);
|
364
375
|
if (event.defaultPrevented) return;
|
365
376
|
reset();
|
366
|
-
resetControlledFields();
|
377
|
+
resetControlledFields(formId);
|
367
378
|
}}
|
368
379
|
>
|
369
380
|
<InternalFormContext.Provider value={contextValue}>
|
package/src/internal/hooks.ts
CHANGED
@@ -6,8 +6,8 @@ import { FieldErrors, ValidationErrorResponseData } from "..";
|
|
6
6
|
import { formDefaultValuesKey } from "./constants";
|
7
7
|
import { InternalFormContext, InternalFormContextValue } from "./formContext";
|
8
8
|
import { Hydratable, hydratable } from "./hydratable";
|
9
|
-
import { InternalFormId } from "./state/storeFamily";
|
10
9
|
import { useFormStore } from "./state/storeHooks";
|
10
|
+
import { InternalFormId } from "./state/types";
|
11
11
|
|
12
12
|
export const useInternalFormContext = (
|
13
13
|
formId?: string | symbol,
|
@@ -1,91 +1,112 @@
|
|
1
|
-
import invariant from "tiny-invariant";
|
2
1
|
import create from "zustand";
|
3
2
|
import { immer } from "zustand/middleware/immer";
|
4
|
-
import {
|
3
|
+
import { InternalFormId } from "./types";
|
4
|
+
|
5
|
+
export type FieldState = {
|
6
|
+
refCount: number;
|
7
|
+
value: unknown;
|
8
|
+
defaultValue?: unknown;
|
9
|
+
hydrated: boolean;
|
10
|
+
valueUpdatePromise: Promise<void> | undefined;
|
11
|
+
resolveValueUpdate: (() => void) | undefined;
|
12
|
+
};
|
5
13
|
|
6
14
|
export type ControlledFieldState = {
|
7
|
-
|
8
|
-
[
|
9
|
-
|
|
10
|
-
|
11
|
-
value: unknown;
|
12
|
-
defaultValue?: unknown;
|
13
|
-
hydrated: boolean;
|
14
|
-
valueUpdatePromise: Promise<void> | undefined;
|
15
|
-
resolveValueUpdate: (() => void) | undefined;
|
16
|
-
}
|
17
|
-
| undefined;
|
15
|
+
forms: {
|
16
|
+
[formId: InternalFormId]: {
|
17
|
+
[fieldName: string]: FieldState | undefined;
|
18
|
+
};
|
18
19
|
};
|
19
|
-
register: (fieldName: string) => void;
|
20
|
-
unregister: (fieldName: string) => void;
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
register: (formId: InternalFormId, fieldName: string) => void;
|
21
|
+
unregister: (formId: InternalFormId, fieldName: string) => void;
|
22
|
+
getField: (
|
23
|
+
formId: InternalFormId,
|
24
|
+
fieldName: string
|
25
|
+
) => FieldState | undefined;
|
26
|
+
setValue: (formId: InternalFormId, fieldName: string, value: unknown) => void;
|
27
|
+
hydrateWithDefault: (
|
28
|
+
formId: InternalFormId,
|
29
|
+
fieldName: string,
|
30
|
+
defaultValue: unknown
|
31
|
+
) => void;
|
32
|
+
awaitValueUpdate: (
|
33
|
+
formId: InternalFormId,
|
34
|
+
fieldName: string
|
35
|
+
) => Promise<void>;
|
36
|
+
reset: (formId: InternalFormId) => void;
|
25
37
|
};
|
26
38
|
|
27
|
-
export const
|
28
|
-
|
29
|
-
|
30
|
-
|
39
|
+
export const useControlledFieldStore = create<ControlledFieldState>()(
|
40
|
+
immer((set, get) => ({
|
41
|
+
forms: {},
|
42
|
+
|
43
|
+
register: (formId, field) =>
|
44
|
+
set((state) => {
|
45
|
+
if (!state.forms[formId]) {
|
46
|
+
state.forms[formId] = {};
|
47
|
+
}
|
48
|
+
|
49
|
+
if (state.forms[formId][field]) {
|
50
|
+
state.forms[formId][field]!.refCount++;
|
51
|
+
} else {
|
52
|
+
state.forms[formId][field] = {
|
53
|
+
refCount: 1,
|
54
|
+
value: undefined,
|
55
|
+
hydrated: false,
|
56
|
+
valueUpdatePromise: undefined,
|
57
|
+
resolveValueUpdate: undefined,
|
58
|
+
};
|
59
|
+
}
|
60
|
+
}),
|
31
61
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
state.fields[field] = {
|
38
|
-
refCount: 1,
|
39
|
-
value: undefined,
|
40
|
-
hydrated: false,
|
41
|
-
valueUpdatePromise: undefined,
|
42
|
-
resolveValueUpdate: undefined,
|
43
|
-
};
|
44
|
-
}
|
45
|
-
}),
|
62
|
+
unregister: (formId, field) =>
|
63
|
+
set((state) => {
|
64
|
+
const formState = state.forms?.[formId];
|
65
|
+
const fieldState = formState?.[field];
|
66
|
+
if (!fieldState) return;
|
46
67
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
if (!fieldState) return;
|
68
|
+
fieldState.refCount--;
|
69
|
+
if (fieldState.refCount === 0) delete formState[field];
|
70
|
+
}),
|
51
71
|
|
52
|
-
|
53
|
-
|
54
|
-
|
72
|
+
getField: (formId, field) => {
|
73
|
+
return get().forms?.[formId]?.[field];
|
74
|
+
},
|
55
75
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
76
|
+
setValue: (formId, field, value) =>
|
77
|
+
set((state) => {
|
78
|
+
const fieldState = state.forms?.[formId]?.[field];
|
79
|
+
if (!fieldState) return;
|
60
80
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
81
|
+
fieldState.value = value;
|
82
|
+
const promise = new Promise<void>((resolve) => {
|
83
|
+
fieldState.resolveValueUpdate = resolve;
|
84
|
+
});
|
85
|
+
fieldState.valueUpdatePromise = promise;
|
86
|
+
}),
|
67
87
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
88
|
+
hydrateWithDefault: (formId, field, defaultValue) =>
|
89
|
+
set((state) => {
|
90
|
+
const fieldState = state.forms[formId]?.[field];
|
91
|
+
if (!fieldState) return;
|
72
92
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
93
|
+
fieldState.value = defaultValue;
|
94
|
+
fieldState.defaultValue = defaultValue;
|
95
|
+
fieldState.hydrated = true;
|
96
|
+
}),
|
77
97
|
|
78
|
-
|
79
|
-
|
80
|
-
|
98
|
+
awaitValueUpdate: async (formId, field) => {
|
99
|
+
await get().getField(formId, field)?.valueUpdatePromise;
|
100
|
+
},
|
81
101
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
102
|
+
reset: (formId) =>
|
103
|
+
set((state) => {
|
104
|
+
const formState = state.forms[formId];
|
105
|
+
if (!formState) return;
|
106
|
+
Object.values(formState).forEach((field) => {
|
107
|
+
if (!field) return;
|
108
|
+
field.value = field.defaultValue;
|
109
|
+
});
|
110
|
+
}),
|
111
|
+
}))
|
91
112
|
);
|
@@ -1,31 +1,37 @@
|
|
1
1
|
import { useCallback, useEffect } from "react";
|
2
2
|
import { InternalFormContextValue } from "../formContext";
|
3
3
|
import { useFieldDefaultValue } from "../hooks";
|
4
|
-
import {
|
5
|
-
import {
|
6
|
-
import { InternalFormId } from "./
|
4
|
+
import { useControlledFieldStore } from "./controlledFieldStore";
|
5
|
+
import { useFormStore } from "./storeHooks";
|
6
|
+
import { InternalFormId } from "./types";
|
7
7
|
|
8
8
|
export const useControlledFieldValue = (
|
9
9
|
context: InternalFormContextValue,
|
10
10
|
field: string
|
11
11
|
) => {
|
12
|
-
const
|
13
|
-
|
12
|
+
const value = useControlledFieldStore(
|
13
|
+
(state) => state.getField(context.formId, field)?.value
|
14
|
+
);
|
14
15
|
|
15
|
-
const
|
16
|
-
|
16
|
+
const isFormHydrated = useFormStore(
|
17
|
+
context.formId,
|
18
|
+
(state) => state.isHydrated
|
19
|
+
);
|
17
20
|
const defaultValue = useFieldDefaultValue(field, context);
|
18
21
|
|
19
|
-
const isFieldHydrated =
|
20
|
-
(state) => state.
|
22
|
+
const isFieldHydrated = useControlledFieldStore(
|
23
|
+
(state) => state.getField(context.formId, field)?.hydrated ?? false
|
24
|
+
);
|
25
|
+
const hydrateWithDefault = useControlledFieldStore(
|
26
|
+
(state) => state.hydrateWithDefault
|
21
27
|
);
|
22
|
-
const hydrateWithDefault = useValueStore((state) => state.hydrateWithDefault);
|
23
28
|
|
24
29
|
useEffect(() => {
|
25
30
|
if (isFormHydrated && !isFieldHydrated) {
|
26
|
-
hydrateWithDefault(field, defaultValue);
|
31
|
+
hydrateWithDefault(context.formId, field, defaultValue);
|
27
32
|
}
|
28
33
|
}, [
|
34
|
+
context.formId,
|
29
35
|
defaultValue,
|
30
36
|
field,
|
31
37
|
hydrateWithDefault,
|
@@ -40,26 +46,26 @@ export const useControllableValue = (
|
|
40
46
|
context: InternalFormContextValue,
|
41
47
|
field: string
|
42
48
|
) => {
|
43
|
-
const
|
44
|
-
|
45
|
-
const resolveUpdate = useValueStore(
|
46
|
-
(state) => state.fields[field]?.resolveValueUpdate
|
49
|
+
const resolveUpdate = useControlledFieldStore(
|
50
|
+
(state) => state.getField(context.formId, field)?.resolveValueUpdate
|
47
51
|
);
|
48
52
|
useEffect(() => {
|
49
53
|
resolveUpdate?.();
|
50
54
|
}, [resolveUpdate]);
|
51
55
|
|
52
|
-
const register =
|
53
|
-
const unregister =
|
56
|
+
const register = useControlledFieldStore((state) => state.register);
|
57
|
+
const unregister = useControlledFieldStore((state) => state.unregister);
|
54
58
|
useEffect(() => {
|
55
|
-
register(field);
|
56
|
-
return () => unregister(field);
|
59
|
+
register(context.formId, field);
|
60
|
+
return () => unregister(context.formId, field);
|
57
61
|
}, [context.formId, field, register, unregister]);
|
58
62
|
|
59
|
-
const setControlledFieldValue =
|
63
|
+
const setControlledFieldValue = useControlledFieldStore(
|
64
|
+
(state) => state.setValue
|
65
|
+
);
|
60
66
|
const setValue = useCallback(
|
61
|
-
(value: unknown) => setControlledFieldValue(field, value),
|
62
|
-
[field, setControlledFieldValue]
|
67
|
+
(value: unknown) => setControlledFieldValue(context.formId, field, value),
|
68
|
+
[context.formId, field, setControlledFieldValue]
|
63
69
|
);
|
64
70
|
|
65
71
|
const value = useControlledFieldValue(context, field);
|
@@ -68,11 +74,17 @@ export const useControllableValue = (
|
|
68
74
|
};
|
69
75
|
|
70
76
|
export const useUpdateControllableValue = (formId: InternalFormId) => {
|
71
|
-
const
|
72
|
-
return
|
77
|
+
const setValue = useControlledFieldStore((state) => state.setValue);
|
78
|
+
return useCallback(
|
79
|
+
(field: string, value: unknown) => setValue(formId, field, value),
|
80
|
+
[formId, setValue]
|
81
|
+
);
|
73
82
|
};
|
74
83
|
|
75
84
|
export const useAwaitValue = (formId: InternalFormId) => {
|
76
|
-
const
|
77
|
-
return
|
85
|
+
const awaitValue = useControlledFieldStore((state) => state.awaitValueUpdate);
|
86
|
+
return useCallback(
|
87
|
+
(field: string) => awaitValue(formId, field),
|
88
|
+
[awaitValue, formId]
|
89
|
+
);
|
78
90
|
};
|