remix-validated-form 4.3.1-beta.0 → 4.4.2
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 +5 -5
- package/browser/ValidatedForm.js +20 -35
- package/browser/hooks.d.ts +1 -1
- package/browser/hooks.js +10 -9
- package/browser/internal/hooks.d.ts +20 -9
- package/browser/internal/hooks.js +32 -23
- package/browser/internal/logic/getRadioChecked.js +1 -1
- package/browser/internal/state/cleanup.d.ts +2 -0
- package/browser/internal/state/cleanup.js +6 -0
- package/browser/internal/state/controlledFieldStore.d.ts +24 -0
- package/browser/internal/state/controlledFieldStore.js +57 -0
- package/browser/internal/state/controlledFields.d.ts +3 -116
- package/browser/internal/state/controlledFields.js +25 -68
- package/browser/internal/state/createFormStore.d.ts +40 -0
- package/browser/internal/state/createFormStore.js +83 -0
- package/browser/internal/state/storeFamily.d.ts +9 -0
- package/browser/internal/state/storeFamily.js +18 -0
- package/browser/internal/state/storeHooks.d.ts +5 -0
- package/browser/internal/state/storeHooks.js +10 -0
- package/browser/unreleased/formStateHooks.d.ts +15 -0
- package/browser/unreleased/formStateHooks.js +23 -14
- package/browser/userFacingFormContext.d.ts +15 -0
- package/browser/userFacingFormContext.js +6 -4
- package/dist/remix-validated-form.cjs.js +18 -1
- package/dist/remix-validated-form.cjs.js.map +1 -0
- package/dist/remix-validated-form.es.js +1039 -1729
- package/dist/remix-validated-form.es.js.map +1 -0
- package/dist/remix-validated-form.umd.js +18 -1
- 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 +20 -9
- package/dist/types/internal/state/cleanup.d.ts +2 -0
- package/dist/types/internal/state/controlledFieldStore.d.ts +24 -0
- package/dist/types/internal/state/controlledFields.d.ts +3 -116
- package/dist/types/internal/state/createFormStore.d.ts +40 -0
- package/dist/types/internal/state/storeFamily.d.ts +9 -0
- package/dist/types/internal/state/storeHooks.d.ts +5 -0
- package/dist/types/unreleased/formStateHooks.d.ts +15 -0
- package/dist/types/userFacingFormContext.d.ts +15 -0
- package/package.json +4 -3
- package/src/ValidatedForm.tsx +38 -53
- package/src/hooks.ts +15 -18
- package/src/internal/hooks.ts +69 -45
- package/src/internal/logic/getRadioChecked.ts +1 -1
- package/src/internal/state/cleanup.ts +8 -0
- package/src/internal/state/controlledFieldStore.ts +91 -0
- package/src/internal/state/controlledFields.ts +31 -123
- package/src/internal/state/createFormStore.ts +152 -0
- package/src/internal/state/storeFamily.ts +24 -0
- package/src/internal/state/storeHooks.ts +22 -0
- package/src/unreleased/formStateHooks.ts +50 -27
- package/src/userFacingFormContext.ts +26 -5
- package/dist/types/internal/reset.d.ts +0 -28
- package/dist/types/internal/state/atomUtils.d.ts +0 -38
- package/dist/types/internal/state.d.ts +0 -343
- package/src/internal/reset.ts +0 -26
- package/src/internal/state/atomUtils.ts +0 -13
- package/src/internal/state.ts +0 -124
@@ -1,93 +1,50 @@
|
|
1
|
-
import { atom } from "jotai";
|
2
|
-
import { useAtomCallback } from "jotai/utils";
|
3
|
-
import omit from "lodash/omit";
|
4
1
|
import { useCallback, useEffect } from "react";
|
5
|
-
import { useFieldDefaultValue
|
6
|
-
import {
|
7
|
-
import {
|
8
|
-
export const controlledFieldsAtom = formAtomFamily({});
|
9
|
-
const refCountAtom = fieldAtomFamily(() => atom(0));
|
10
|
-
const fieldValueAtom = fieldAtomFamily(() => atom(undefined));
|
11
|
-
const fieldValueHydratedAtom = fieldAtomFamily(() => atom(false));
|
12
|
-
export const valueUpdatePromiseAtom = fieldAtomFamily(() => atom(undefined));
|
13
|
-
export const resolveValueUpdateAtom = fieldAtomFamily(() => atom(undefined));
|
14
|
-
const registerAtom = atom(null, (get, set, { formId, field }) => {
|
15
|
-
set(refCountAtom({ formId, field }), (prev) => prev + 1);
|
16
|
-
const newRefCount = get(refCountAtom({ formId, field }));
|
17
|
-
// We don't set hydrated here because it gets set when we know
|
18
|
-
// we have the right default values
|
19
|
-
if (newRefCount === 1) {
|
20
|
-
set(controlledFieldsAtom(formId), (prev) => ({
|
21
|
-
...prev,
|
22
|
-
[field]: fieldValueAtom({ formId, field }),
|
23
|
-
}));
|
24
|
-
}
|
25
|
-
});
|
26
|
-
const unregisterAtom = atom(null, (get, set, { formId, field }) => {
|
27
|
-
set(refCountAtom({ formId, field }), (prev) => prev - 1);
|
28
|
-
const newRefCount = get(refCountAtom({ formId, field }));
|
29
|
-
if (newRefCount === 0) {
|
30
|
-
set(controlledFieldsAtom(formId), (prev) => omit(prev, field));
|
31
|
-
fieldValueAtom.remove({ formId, field });
|
32
|
-
resolveValueUpdateAtom.remove({ formId, field });
|
33
|
-
fieldValueHydratedAtom.remove({ formId, field });
|
34
|
-
}
|
35
|
-
});
|
36
|
-
export const setControlledFieldValueAtom = atom(null, (_get, set, { formId, field, value, }) => {
|
37
|
-
set(fieldValueAtom({ formId, field }), value);
|
38
|
-
const resolveAtom = resolveValueUpdateAtom({ formId, field });
|
39
|
-
const promiseAtom = valueUpdatePromiseAtom({ formId, field });
|
40
|
-
const promise = new Promise((resolve) => set(resolveAtom, () => {
|
41
|
-
resolve();
|
42
|
-
set(resolveAtom, undefined);
|
43
|
-
set(promiseAtom, undefined);
|
44
|
-
}));
|
45
|
-
set(promiseAtom, promise);
|
46
|
-
});
|
2
|
+
import { useFieldDefaultValue } from "../hooks";
|
3
|
+
import { controlledFieldStore } from "./controlledFieldStore";
|
4
|
+
import { formStore } from "./createFormStore";
|
47
5
|
export const useControlledFieldValue = (context, field) => {
|
48
|
-
const
|
49
|
-
const
|
6
|
+
const useValueStore = controlledFieldStore(context.formId);
|
7
|
+
const value = useValueStore((state) => { var _a; return (_a = state.fields[field]) === null || _a === void 0 ? void 0 : _a.value; });
|
8
|
+
const useFormStore = formStore(context.formId);
|
9
|
+
const isFormHydrated = useFormStore((state) => state.isHydrated);
|
50
10
|
const defaultValue = useFieldDefaultValue(field, context);
|
51
|
-
const
|
52
|
-
const
|
11
|
+
const isFieldHydrated = useValueStore((state) => { var _a, _b; return (_b = (_a = state.fields[field]) === null || _a === void 0 ? void 0 : _a.hydrated) !== null && _b !== void 0 ? _b : false; });
|
12
|
+
const hydrateWithDefault = useValueStore((state) => state.hydrateWithDefault);
|
53
13
|
useEffect(() => {
|
54
|
-
if (
|
55
|
-
|
56
|
-
setIsFieldHydrated(true);
|
14
|
+
if (isFormHydrated && !isFieldHydrated) {
|
15
|
+
hydrateWithDefault(field, defaultValue);
|
57
16
|
}
|
58
17
|
}, [
|
59
18
|
defaultValue,
|
60
19
|
field,
|
61
|
-
|
20
|
+
hydrateWithDefault,
|
62
21
|
isFieldHydrated,
|
63
|
-
|
64
|
-
setIsFieldHydrated,
|
65
|
-
setValue,
|
22
|
+
isFormHydrated,
|
66
23
|
]);
|
67
24
|
return isFieldHydrated ? value : defaultValue;
|
68
25
|
};
|
69
26
|
export const useControllableValue = (context, field) => {
|
70
|
-
const
|
27
|
+
const useValueStore = controlledFieldStore(context.formId);
|
28
|
+
const resolveUpdate = useValueStore((state) => { var _a; return (_a = state.fields[field]) === null || _a === void 0 ? void 0 : _a.resolveValueUpdate; });
|
71
29
|
useEffect(() => {
|
72
30
|
resolveUpdate === null || resolveUpdate === void 0 ? void 0 : resolveUpdate();
|
73
31
|
}, [resolveUpdate]);
|
74
|
-
const register =
|
75
|
-
const unregister =
|
32
|
+
const register = useValueStore((state) => state.register);
|
33
|
+
const unregister = useValueStore((state) => state.unregister);
|
76
34
|
useEffect(() => {
|
77
|
-
register(
|
78
|
-
return () => unregister(
|
35
|
+
register(field);
|
36
|
+
return () => unregister(field);
|
79
37
|
}, [context.formId, field, register, unregister]);
|
80
|
-
const setControlledFieldValue =
|
81
|
-
const setValue = useCallback((value) => setControlledFieldValue(
|
38
|
+
const setControlledFieldValue = useValueStore((state) => state.setValue);
|
39
|
+
const setValue = useCallback((value) => setControlledFieldValue(field, value), [field, setControlledFieldValue]);
|
82
40
|
const value = useControlledFieldValue(context, field);
|
83
41
|
return [value, setValue];
|
84
42
|
};
|
85
43
|
export const useUpdateControllableValue = (formId) => {
|
86
|
-
const
|
87
|
-
return
|
44
|
+
const useValueStore = controlledFieldStore(formId);
|
45
|
+
return useValueStore((state) => state.setValue);
|
88
46
|
};
|
89
47
|
export const useAwaitValue = (formId) => {
|
90
|
-
|
91
|
-
|
92
|
-
}, [formId]));
|
48
|
+
const useValueStore = controlledFieldStore(formId);
|
49
|
+
return useValueStore((state) => state.awaitValueUpdate);
|
93
50
|
};
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import { FieldErrors, TouchedFields, Validator } from "../../validation/types";
|
2
|
+
export declare type SyncedFormProps = {
|
3
|
+
formId?: string;
|
4
|
+
action?: string;
|
5
|
+
subaction?: string;
|
6
|
+
defaultValues: {
|
7
|
+
[fieldName: string]: any;
|
8
|
+
};
|
9
|
+
registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
|
10
|
+
validator: Validator<unknown>;
|
11
|
+
};
|
12
|
+
export declare type FormState = {
|
13
|
+
isHydrated: boolean;
|
14
|
+
isSubmitting: boolean;
|
15
|
+
hasBeenSubmitted: boolean;
|
16
|
+
fieldErrors: FieldErrors;
|
17
|
+
touchedFields: TouchedFields;
|
18
|
+
formProps?: SyncedFormProps;
|
19
|
+
formElement: HTMLFormElement | null;
|
20
|
+
isValid: () => boolean;
|
21
|
+
startSubmit: () => void;
|
22
|
+
endSubmit: () => void;
|
23
|
+
setTouched: (field: string, touched: boolean) => void;
|
24
|
+
setFieldError: (field: string, error: string) => void;
|
25
|
+
setFieldErrors: (errors: FieldErrors) => void;
|
26
|
+
clearFieldError: (field: string) => void;
|
27
|
+
reset: () => void;
|
28
|
+
syncFormProps: (props: SyncedFormProps) => void;
|
29
|
+
setHydrated: () => void;
|
30
|
+
setFormElement: (formElement: HTMLFormElement | null) => void;
|
31
|
+
validateField: (fieldName: string) => Promise<string | null>;
|
32
|
+
validate: () => Promise<void>;
|
33
|
+
resetFormElement: () => void;
|
34
|
+
};
|
35
|
+
export declare const formStore: {
|
36
|
+
(formId: import("./storeFamily").InternalFormId): import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<FormState>, "setState"> & {
|
37
|
+
setState(nextStateOrUpdater: FormState | Partial<FormState> | ((state: import("immer/dist/internal").WritableDraft<FormState>) => void), shouldReplace?: boolean | undefined): void;
|
38
|
+
}>;
|
39
|
+
remove(formId: import("./storeFamily").InternalFormId): void;
|
40
|
+
};
|
@@ -0,0 +1,83 @@
|
|
1
|
+
import invariant from "tiny-invariant";
|
2
|
+
import create from "zustand";
|
3
|
+
import { immer } from "zustand/middleware/immer";
|
4
|
+
import { controlledFieldStore } from "./controlledFieldStore";
|
5
|
+
import { storeFamily } from "./storeFamily";
|
6
|
+
export const formStore = storeFamily((formId) => create()(immer((set, get, api) => ({
|
7
|
+
isHydrated: false,
|
8
|
+
isSubmitting: false,
|
9
|
+
hasBeenSubmitted: false,
|
10
|
+
touchedFields: {},
|
11
|
+
fieldErrors: {},
|
12
|
+
formElement: null,
|
13
|
+
isValid: () => Object.keys(get().fieldErrors).length === 0,
|
14
|
+
startSubmit: () => set((state) => {
|
15
|
+
state.isSubmitting = true;
|
16
|
+
state.hasBeenSubmitted = true;
|
17
|
+
}),
|
18
|
+
endSubmit: () => set((state) => {
|
19
|
+
state.isSubmitting = false;
|
20
|
+
}),
|
21
|
+
setTouched: (fieldName, touched) => set((state) => {
|
22
|
+
state.touchedFields[fieldName] = touched;
|
23
|
+
}),
|
24
|
+
setFieldError: (fieldName, error) => set((state) => {
|
25
|
+
state.fieldErrors[fieldName] = error;
|
26
|
+
}),
|
27
|
+
setFieldErrors: (errors) => set((state) => {
|
28
|
+
state.fieldErrors = errors;
|
29
|
+
}),
|
30
|
+
clearFieldError: (fieldName) => set((state) => {
|
31
|
+
delete state.fieldErrors[fieldName];
|
32
|
+
}),
|
33
|
+
reset: () => set((state) => {
|
34
|
+
state.fieldErrors = {};
|
35
|
+
state.touchedFields = {};
|
36
|
+
state.hasBeenSubmitted = false;
|
37
|
+
}),
|
38
|
+
syncFormProps: (props) => set((state) => {
|
39
|
+
state.formProps = props;
|
40
|
+
}),
|
41
|
+
setHydrated: () => set((state) => {
|
42
|
+
state.isHydrated = true;
|
43
|
+
}),
|
44
|
+
setFormElement: (formElement) => {
|
45
|
+
// This gets called frequently, so we want to avoid calling set() every time
|
46
|
+
// Or else we wind up with an infinite loop
|
47
|
+
if (get().formElement === formElement)
|
48
|
+
return;
|
49
|
+
set((state) => {
|
50
|
+
// weird type issue here
|
51
|
+
// seems to be because formElement is a writable draft
|
52
|
+
state.formElement = formElement;
|
53
|
+
});
|
54
|
+
},
|
55
|
+
validateField: async (field) => {
|
56
|
+
var _a, _b, _c;
|
57
|
+
const formElement = get().formElement;
|
58
|
+
invariant(formElement, "Cannot find reference to form. This is probably a bug in remix-validated-form.");
|
59
|
+
const validator = (_a = get().formProps) === null || _a === void 0 ? void 0 : _a.validator;
|
60
|
+
invariant(validator, "Cannot validator. This is probably a bug in remix-validated-form.");
|
61
|
+
await ((_c = (_b = controlledFieldStore(formId).getState()).awaitValueUpdate) === null || _c === void 0 ? void 0 : _c.call(_b, field));
|
62
|
+
const { error } = await validator.validateField(new FormData(formElement), field);
|
63
|
+
if (error) {
|
64
|
+
get().setFieldError(field, error);
|
65
|
+
return error;
|
66
|
+
}
|
67
|
+
else {
|
68
|
+
get().clearFieldError(field);
|
69
|
+
return null;
|
70
|
+
}
|
71
|
+
},
|
72
|
+
validate: async () => {
|
73
|
+
var _a;
|
74
|
+
const formElement = get().formElement;
|
75
|
+
invariant(formElement, "Cannot find reference to form. This is probably a bug in remix-validated-form.");
|
76
|
+
const validator = (_a = get().formProps) === null || _a === void 0 ? void 0 : _a.validator;
|
77
|
+
invariant(validator, "Cannot validator. This is probably a bug in remix-validated-form.");
|
78
|
+
const { error } = await validator.validate(new FormData(formElement));
|
79
|
+
if (error)
|
80
|
+
get().setFieldErrors(error.fieldErrors);
|
81
|
+
},
|
82
|
+
resetFormElement: () => { var _a; return (_a = get().formElement) === null || _a === void 0 ? void 0 : _a.reset(); },
|
83
|
+
}))));
|
@@ -0,0 +1,9 @@
|
|
1
|
+
/**
|
2
|
+
* This is basically what `atomFamily` from jotai does,
|
3
|
+
* but it doesn't make sense to include the entire jotai library just for that api.
|
4
|
+
*/
|
5
|
+
export declare type InternalFormId = string | symbol;
|
6
|
+
export declare const storeFamily: <T>(create: (formId: InternalFormId) => T) => {
|
7
|
+
(formId: InternalFormId): T;
|
8
|
+
remove(formId: InternalFormId): void;
|
9
|
+
};
|
@@ -0,0 +1,18 @@
|
|
1
|
+
/**
|
2
|
+
* This is basically what `atomFamily` from jotai does,
|
3
|
+
* but it doesn't make sense to include the entire jotai library just for that api.
|
4
|
+
*/
|
5
|
+
export const storeFamily = (create) => {
|
6
|
+
const stores = new Map();
|
7
|
+
const family = (formId) => {
|
8
|
+
if (stores.has(formId))
|
9
|
+
return stores.get(formId);
|
10
|
+
const store = create(formId);
|
11
|
+
stores.set(formId, store);
|
12
|
+
return store;
|
13
|
+
};
|
14
|
+
family.remove = (formId) => {
|
15
|
+
stores.delete(formId);
|
16
|
+
};
|
17
|
+
return family;
|
18
|
+
};
|
@@ -0,0 +1,5 @@
|
|
1
|
+
import { ControlledFieldState } from "./controlledFieldStore";
|
2
|
+
import { FormState } from "./createFormStore";
|
3
|
+
import { InternalFormId } from "./storeFamily";
|
4
|
+
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,10 @@
|
|
1
|
+
import { controlledFieldStore, } from "./controlledFieldStore";
|
2
|
+
import { formStore } from "./createFormStore";
|
3
|
+
export const useFormStore = (formId, selector) => {
|
4
|
+
const useStore = formStore(formId);
|
5
|
+
return useStore(selector);
|
6
|
+
};
|
7
|
+
export const useControlledFieldStore = (formId, selector) => {
|
8
|
+
const useStore = controlledFieldStore(formId);
|
9
|
+
return useStore(selector);
|
10
|
+
};
|
@@ -30,6 +30,21 @@ export declare type FormHelpers = {
|
|
30
30
|
* Change the touched state of the specified field.
|
31
31
|
*/
|
32
32
|
setTouched: (fieldName: string, touched: boolean) => void;
|
33
|
+
/**
|
34
|
+
* Validate the whole form and populate any errors.
|
35
|
+
*/
|
36
|
+
validate: () => Promise<void>;
|
37
|
+
/**
|
38
|
+
* Clears all errors on the form.
|
39
|
+
*/
|
40
|
+
clearAllErrors: () => void;
|
41
|
+
/**
|
42
|
+
* Resets the form.
|
43
|
+
*
|
44
|
+
* _Note_: The equivalent behavior can be achieved by calling formElement.reset()
|
45
|
+
* or clicking a button element with `type="reset"`.
|
46
|
+
*/
|
47
|
+
reset: () => void;
|
33
48
|
};
|
34
49
|
/**
|
35
50
|
* Returns helpers that can be used to update the form state.
|
@@ -1,25 +1,27 @@
|
|
1
1
|
import { useMemo } from "react";
|
2
|
-
import { useInternalFormContext, useClearError, useSetTouched, useDefaultValuesForForm, useFieldErrorsForForm,
|
3
|
-
import { fieldErrorsAtom, formPropsAtom, hasBeenSubmittedAtom, isSubmittingAtom, isValidAtom, touchedFieldsAtom, } from "../internal/state";
|
2
|
+
import { useInternalFormContext, useClearError, useSetTouched, useDefaultValuesForForm, useFieldErrorsForForm, useInternalIsSubmitting, useInternalHasBeenSubmitted, useTouchedFields, useInternalIsValid, useFieldErrors, useValidateField, useValidate, useSetFieldErrors, useResetFormElement, useSyncedDefaultValues, useFormActionProp, useFormSubactionProp, } from "../internal/hooks";
|
4
3
|
/**
|
5
4
|
* Returns information about the form.
|
6
5
|
*
|
7
6
|
* @param formId the id of the form. Only necessary if being used outside a ValidatedForm.
|
8
7
|
*/
|
9
8
|
export const useFormState = (formId) => {
|
10
|
-
const formContext = useInternalFormContext(formId, "
|
11
|
-
const
|
12
|
-
const
|
13
|
-
const
|
14
|
-
const
|
15
|
-
const
|
9
|
+
const formContext = useInternalFormContext(formId, "useFormState");
|
10
|
+
const isSubmitting = useInternalIsSubmitting(formContext.formId);
|
11
|
+
const hasBeenSubmitted = useInternalHasBeenSubmitted(formContext.formId);
|
12
|
+
const touchedFields = useTouchedFields(formContext.formId);
|
13
|
+
const isValid = useInternalIsValid(formContext.formId);
|
14
|
+
const action = useFormActionProp(formContext.formId);
|
15
|
+
const subaction = useFormSubactionProp(formContext.formId);
|
16
|
+
const syncedDefaultValues = useSyncedDefaultValues(formContext.formId);
|
16
17
|
const defaultValuesToUse = useDefaultValuesForForm(formContext);
|
17
|
-
const hydratedDefaultValues = defaultValuesToUse.hydrateTo(
|
18
|
-
const fieldErrorsFromState =
|
18
|
+
const hydratedDefaultValues = defaultValuesToUse.hydrateTo(syncedDefaultValues);
|
19
|
+
const fieldErrorsFromState = useFieldErrors(formContext.formId);
|
19
20
|
const fieldErrorsToUse = useFieldErrorsForForm(formContext);
|
20
21
|
const hydratedFieldErrors = fieldErrorsToUse.hydrateTo(fieldErrorsFromState);
|
21
22
|
return useMemo(() => ({
|
22
|
-
|
23
|
+
action,
|
24
|
+
subaction,
|
23
25
|
defaultValues: hydratedDefaultValues,
|
24
26
|
fieldErrors: hydratedFieldErrors !== null && hydratedFieldErrors !== void 0 ? hydratedFieldErrors : {},
|
25
27
|
hasBeenSubmitted,
|
@@ -27,12 +29,13 @@ export const useFormState = (formId) => {
|
|
27
29
|
touchedFields,
|
28
30
|
isValid,
|
29
31
|
}), [
|
30
|
-
|
32
|
+
action,
|
31
33
|
hasBeenSubmitted,
|
32
34
|
hydratedDefaultValues,
|
33
35
|
hydratedFieldErrors,
|
34
36
|
isSubmitting,
|
35
37
|
isValid,
|
38
|
+
subaction,
|
36
39
|
touchedFields,
|
37
40
|
]);
|
38
41
|
};
|
@@ -44,11 +47,17 @@ export const useFormState = (formId) => {
|
|
44
47
|
export const useFormHelpers = (formId) => {
|
45
48
|
const formContext = useInternalFormContext(formId, "useFormHelpers");
|
46
49
|
const setTouched = useSetTouched(formContext);
|
47
|
-
const
|
50
|
+
const validateField = useValidateField(formContext.formId);
|
51
|
+
const validate = useValidate(formContext.formId);
|
48
52
|
const clearError = useClearError(formContext);
|
53
|
+
const setFieldErrors = useSetFieldErrors(formContext.formId);
|
54
|
+
const reset = useResetFormElement(formContext.formId);
|
49
55
|
return useMemo(() => ({
|
50
56
|
setTouched,
|
51
57
|
validateField,
|
52
58
|
clearError,
|
53
|
-
|
59
|
+
validate,
|
60
|
+
clearAllErrors: () => setFieldErrors({}),
|
61
|
+
reset,
|
62
|
+
}), [clearError, reset, setFieldErrors, setTouched, validate, validateField]);
|
54
63
|
};
|
@@ -53,6 +53,21 @@ export declare type FormContextValue = {
|
|
53
53
|
* Change the touched state of the specified field.
|
54
54
|
*/
|
55
55
|
setFieldTouched: (fieldName: string, touched: boolean) => void;
|
56
|
+
/**
|
57
|
+
* Validate the whole form and populate any errors.
|
58
|
+
*/
|
59
|
+
validate: () => Promise<void>;
|
60
|
+
/**
|
61
|
+
* Clears all errors on the form.
|
62
|
+
*/
|
63
|
+
clearAllErrors: () => void;
|
64
|
+
/**
|
65
|
+
* Resets the form.
|
66
|
+
*
|
67
|
+
* _Note_: The equivalent behavior can be achieved by calling formElement.reset()
|
68
|
+
* or clicking a button element with `type="reset"`.
|
69
|
+
*/
|
70
|
+
reset: () => void;
|
56
71
|
};
|
57
72
|
/**
|
58
73
|
* Provides access to some of the internal state of the form.
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import { useCallback } from "react";
|
2
|
-
import {
|
3
|
-
import { formPropsAtom } from "./internal/state";
|
2
|
+
import { useInternalFormContext, useRegisterReceiveFocus, } from "./internal/hooks";
|
4
3
|
import { useFormHelpers, useFormState } from "./unreleased/formStateHooks";
|
5
4
|
/**
|
6
5
|
* Provides access to some of the internal state of the form.
|
@@ -9,8 +8,8 @@ export const useFormContext = (formId) => {
|
|
9
8
|
// Try to access context so we get our error specific to this hook if it's not there
|
10
9
|
const context = useInternalFormContext(formId, "useFormContext");
|
11
10
|
const state = useFormState(formId);
|
12
|
-
const { clearError: internalClearError, setTouched, validateField, } = useFormHelpers(formId);
|
13
|
-
const
|
11
|
+
const { clearError: internalClearError, setTouched, validateField, clearAllErrors, validate, reset, } = useFormHelpers(formId);
|
12
|
+
const registerReceiveFocus = useRegisterReceiveFocus(context.formId);
|
14
13
|
const clearError = useCallback((...names) => {
|
15
14
|
names.forEach((name) => {
|
16
15
|
internalClearError(name);
|
@@ -22,5 +21,8 @@ export const useFormContext = (formId) => {
|
|
22
21
|
validateField,
|
23
22
|
clearError,
|
24
23
|
registerReceiveFocus,
|
24
|
+
clearAllErrors,
|
25
|
+
validate,
|
26
|
+
reset,
|
25
27
|
};
|
26
28
|
};
|