@octanejs/hook-form 0.1.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/LICENSE +21 -0
- package/README.md +62 -0
- package/package.json +46 -0
- package/src/FormProvider.tsrx +76 -0
- package/src/FormProvider.tsrx.d.ts +10 -0
- package/src/constants.ts +31 -0
- package/src/controller.tsrx +8 -0
- package/src/controller.tsrx.d.ts +52 -0
- package/src/form.tsrx +119 -0
- package/src/form.tsrx.d.ts +6 -0
- package/src/formStateSubscribe.tsrx +10 -0
- package/src/formStateSubscribe.tsrx.d.ts +16 -0
- package/src/index.ts +19 -0
- package/src/logic/appendErrors.ts +19 -0
- package/src/logic/createFormControl.ts +1841 -0
- package/src/logic/generateId.ts +14 -0
- package/src/logic/generateWatchOutput.ts +27 -0
- package/src/logic/getCheckboxValue.ts +36 -0
- package/src/logic/getDirtyFields.ts +120 -0
- package/src/logic/getEventValue.ts +12 -0
- package/src/logic/getFieldValue.ts +33 -0
- package/src/logic/getFieldValueAs.ts +22 -0
- package/src/logic/getFocusFieldName.ts +13 -0
- package/src/logic/getNodeParentName.ts +4 -0
- package/src/logic/getProxyFormState.ts +33 -0
- package/src/logic/getRadioValue.ts +24 -0
- package/src/logic/getResolverOptions.ts +33 -0
- package/src/logic/getRuleValue.ts +16 -0
- package/src/logic/getValidateError.ts +22 -0
- package/src/logic/getValidationModes.ts +11 -0
- package/src/logic/getValueAndMessage.ts +12 -0
- package/src/logic/hasPromiseValidation.ts +27 -0
- package/src/logic/hasValidation.ts +12 -0
- package/src/logic/index.ts +3 -0
- package/src/logic/isNameInFieldArray.ts +7 -0
- package/src/logic/isWatched.ts +11 -0
- package/src/logic/iterateFieldsByAction.ts +37 -0
- package/src/logic/schemaErrorLookup.ts +54 -0
- package/src/logic/shouldRenderFormState.ts +25 -0
- package/src/logic/shouldSubscribeByName.ts +18 -0
- package/src/logic/skipValidation.ts +21 -0
- package/src/logic/unsetEmptyArray.ts +6 -0
- package/src/logic/updateFieldArrayRootError.ts +17 -0
- package/src/logic/validateField.ts +273 -0
- package/src/types/controller.ts +98 -0
- package/src/types/errors.ts +56 -0
- package/src/types/events.ts +26 -0
- package/src/types/fieldArray.ts +288 -0
- package/src/types/fields.ts +46 -0
- package/src/types/form.ts +961 -0
- package/src/types/index.ts +12 -0
- package/src/types/path/common.ts +391 -0
- package/src/types/path/eager.ts +224 -0
- package/src/types/path/index.ts +16 -0
- package/src/types/resolvers.ts +38 -0
- package/src/types/utils.ts +125 -0
- package/src/types/validator.ts +94 -0
- package/src/types/watch.ts +66 -0
- package/src/useController.ts +268 -0
- package/src/useFieldArray.ts +490 -0
- package/src/useForm.ts +182 -0
- package/src/useFormContext.ts +47 -0
- package/src/useFormControlContext.ts +25 -0
- package/src/useFormState.ts +86 -0
- package/src/useIsomorphicLayoutEffect.ts +6 -0
- package/src/useWatch.ts +356 -0
- package/src/utils/append.ts +4 -0
- package/src/utils/cloneObject.ts +32 -0
- package/src/utils/compact.ts +2 -0
- package/src/utils/convertToArrayPayload.ts +2 -0
- package/src/utils/createSubject.ts +48 -0
- package/src/utils/deepEqual.ts +81 -0
- package/src/utils/deepMerge.ts +27 -0
- package/src/utils/extractFormValues.ts +28 -0
- package/src/utils/fillEmptyArray.ts +3 -0
- package/src/utils/flatten.ts +23 -0
- package/src/utils/get.ts +29 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/insert.ts +8 -0
- package/src/utils/isBoolean.ts +2 -0
- package/src/utils/isCheckBoxInput.ts +4 -0
- package/src/utils/isDateObject.ts +2 -0
- package/src/utils/isEmptyObject.ts +7 -0
- package/src/utils/isFileInput.ts +4 -0
- package/src/utils/isFunction.ts +2 -0
- package/src/utils/isHTMLElement.ts +13 -0
- package/src/utils/isKey.ts +4 -0
- package/src/utils/isMultipleSelect.ts +5 -0
- package/src/utils/isNullOrUndefined.ts +2 -0
- package/src/utils/isObject.ts +8 -0
- package/src/utils/isPlainObject.ts +8 -0
- package/src/utils/isPrimitive.ts +8 -0
- package/src/utils/isRadioInput.ts +4 -0
- package/src/utils/isRadioOrCheckbox.ts +8 -0
- package/src/utils/isRegex.ts +2 -0
- package/src/utils/isString.ts +2 -0
- package/src/utils/isUndefined.ts +2 -0
- package/src/utils/isWeb.ts +4 -0
- package/src/utils/live.ts +6 -0
- package/src/utils/move.ts +15 -0
- package/src/utils/noop.ts +2 -0
- package/src/utils/objectHasFunction.ts +11 -0
- package/src/utils/prepend.ts +7 -0
- package/src/utils/remove.ts +24 -0
- package/src/utils/set.ts +36 -0
- package/src/utils/sleep.ts +2 -0
- package/src/utils/stringToPath.ts +4 -0
- package/src/utils/swap.ts +4 -0
- package/src/utils/unset.ts +65 -0
- package/src/utils/update.ts +5 -0
- package/src/watch.tsrx +7 -0
- package/src/watch.tsrx.d.ts +42 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Vendored from react-hook-form@7.81.0 src/useFormContext.tsx (octane port).
|
|
2
|
+
// Upstream's file holds both useFormContext and the FormProvider component;
|
|
3
|
+
// the component half lives in FormProvider.tsrx here (octane components with
|
|
4
|
+
// JSX are authored in .tsrx), and the shared contexts in useFormControlContext.
|
|
5
|
+
import { useContext } from 'octane';
|
|
6
|
+
|
|
7
|
+
import type { FieldValues, UseFormReturn } from './types';
|
|
8
|
+
import { HookFormContext } from './useFormControlContext';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* This custom hook allows you to access the form context. useFormContext is intended to be used in deeply nested structures, where it would become inconvenient to pass the context as a prop. To be used with {@link FormProvider}.
|
|
12
|
+
*
|
|
13
|
+
* @remarks
|
|
14
|
+
* [API](https://react-hook-form.com/docs/useformcontext) • [Demo](https://codesandbox.io/s/react-hook-form-v7-form-context-ytudi)
|
|
15
|
+
*
|
|
16
|
+
* @returns return all useForm methods
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* function App() {
|
|
21
|
+
* const methods = useForm();
|
|
22
|
+
* const onSubmit = data => console.log(data);
|
|
23
|
+
*
|
|
24
|
+
* return (
|
|
25
|
+
* <FormProvider {...methods} >
|
|
26
|
+
* <form onSubmit={methods.handleSubmit(onSubmit)}>
|
|
27
|
+
* <NestedInput />
|
|
28
|
+
* <input type="submit" />
|
|
29
|
+
* </form>
|
|
30
|
+
* </FormProvider>
|
|
31
|
+
* );
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* function NestedInput() {
|
|
35
|
+
* const { register } = useFormContext(); // retrieve all hook methods
|
|
36
|
+
* return <input {...register("test")} />;
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export const useFormContext = <
|
|
41
|
+
TFieldValues extends FieldValues,
|
|
42
|
+
TContext = any,
|
|
43
|
+
TTransformedValues = TFieldValues,
|
|
44
|
+
>(): UseFormReturn<TFieldValues, TContext, TTransformedValues> =>
|
|
45
|
+
useContext(HookFormContext) as UseFormReturn<TFieldValues, TContext, TTransformedValues>;
|
|
46
|
+
|
|
47
|
+
export { FormProvider } from './FormProvider.tsrx';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Vendored from react-hook-form@7.81.0 src/useFormControlContext.ts (octane port).
|
|
2
|
+
// octane: HookFormContext (module-local in upstream's useFormContext.tsx) also
|
|
3
|
+
// lives here so FormProvider.tsrx can import it — this module is internal (not
|
|
4
|
+
// star-exported from index.ts), so neither context leaks into the public API.
|
|
5
|
+
import { createContext, useContext } from 'octane';
|
|
6
|
+
|
|
7
|
+
import type { Control, FieldValues, UseFormReturn } from './types';
|
|
8
|
+
|
|
9
|
+
export const HookFormContext = createContext<UseFormReturn | null>(null);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Separate context for `control` to prevent unnecessary rerenders.
|
|
13
|
+
* Internal hooks that only need control use this instead of full form context.
|
|
14
|
+
*/
|
|
15
|
+
export const HookFormControlContext = createContext<Control | null>(null);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @internal Internal hook to access only control from context.
|
|
19
|
+
*/
|
|
20
|
+
export const useFormControlContext = <
|
|
21
|
+
TFieldValues extends FieldValues,
|
|
22
|
+
TContext = any,
|
|
23
|
+
TTransformedValues = TFieldValues,
|
|
24
|
+
>(): Control<TFieldValues, TContext, TTransformedValues> =>
|
|
25
|
+
useContext(HookFormControlContext) as Control<TFieldValues, TContext, TTransformedValues>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Vendored from react-hook-form@7.81.0 src/useFormState.ts (octane port).
|
|
2
|
+
import { useState, useRef, useEffect, useMemo } from 'octane';
|
|
3
|
+
|
|
4
|
+
import getProxyFormState from './logic/getProxyFormState';
|
|
5
|
+
import type { FieldValues, FormState, UseFormStateProps, UseFormStateReturn } from './types';
|
|
6
|
+
import { useFormControlContext } from './useFormControlContext';
|
|
7
|
+
import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* This custom hook allows you to subscribe to each form state, and isolate the re-render at the custom hook level. It has its scope in terms of form state subscription, so it would not affect other useFormState and useForm. Using this hook can reduce the re-render impact on large and complex form application.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* [API](https://react-hook-form.com/docs/useformstate) • [Demo](https://codesandbox.io/s/useformstate-75xly)
|
|
14
|
+
*
|
|
15
|
+
* @param props - include options on specify fields to subscribe. {@link UseFormStateReturn}
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* function App() {
|
|
20
|
+
* const { register, handleSubmit, control } = useForm({
|
|
21
|
+
* defaultValues: {
|
|
22
|
+
* firstName: "firstName"
|
|
23
|
+
* }});
|
|
24
|
+
* const { dirtyFields } = useFormState({
|
|
25
|
+
* control
|
|
26
|
+
* });
|
|
27
|
+
* const onSubmit = (data) => console.log(data);
|
|
28
|
+
*
|
|
29
|
+
* return (
|
|
30
|
+
* <form onSubmit={handleSubmit(onSubmit)}>
|
|
31
|
+
* <input {...register("firstName")} placeholder="First Name" />
|
|
32
|
+
* {dirtyFields.firstName && <p>Field is dirty.</p>}
|
|
33
|
+
* <input type="submit" />
|
|
34
|
+
* </form>
|
|
35
|
+
* );
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function useFormState<
|
|
40
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
41
|
+
TTransformedValues = TFieldValues,
|
|
42
|
+
>(props?: UseFormStateProps<TFieldValues, TTransformedValues>): UseFormStateReturn<TFieldValues> {
|
|
43
|
+
const formControl = useFormControlContext<TFieldValues, any, TTransformedValues>();
|
|
44
|
+
const { control = formControl, disabled, name, exact } = props || {};
|
|
45
|
+
const [formState, updateFormState] = useState<FormState<TFieldValues>>(() => ({
|
|
46
|
+
...control._formState,
|
|
47
|
+
defaultValues: control._defaultValues as FormState<TFieldValues>['defaultValues'],
|
|
48
|
+
}));
|
|
49
|
+
const _localProxyFormState = useRef({
|
|
50
|
+
isDirty: false,
|
|
51
|
+
isLoading: false,
|
|
52
|
+
dirtyFields: false,
|
|
53
|
+
touchedFields: false,
|
|
54
|
+
validatingFields: false,
|
|
55
|
+
isValidating: false,
|
|
56
|
+
isValid: false,
|
|
57
|
+
errors: false,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
useIsomorphicLayoutEffect(
|
|
61
|
+
() =>
|
|
62
|
+
control._subscribe({
|
|
63
|
+
name,
|
|
64
|
+
formState: _localProxyFormState.current,
|
|
65
|
+
exact,
|
|
66
|
+
callback: (formState) => {
|
|
67
|
+
!disabled &&
|
|
68
|
+
updateFormState({
|
|
69
|
+
...control._formState,
|
|
70
|
+
...formState,
|
|
71
|
+
defaultValues: control._defaultValues as FormState<TFieldValues>['defaultValues'],
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
}),
|
|
75
|
+
[name, disabled, exact],
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
_localProxyFormState.current.isValid && control._setValid(true);
|
|
80
|
+
}, [control]);
|
|
81
|
+
|
|
82
|
+
return useMemo(
|
|
83
|
+
() => getProxyFormState(formState, control, _localProxyFormState.current, false),
|
|
84
|
+
[formState, control],
|
|
85
|
+
);
|
|
86
|
+
}
|
package/src/useWatch.ts
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
// Vendored from react-hook-form@7.81.0 src/useWatch.ts (octane port).
|
|
2
|
+
import { useState, useRef, useEffect, useMemo, useCallback } from 'octane';
|
|
3
|
+
|
|
4
|
+
import generateWatchOutput from './logic/generateWatchOutput';
|
|
5
|
+
import deepEqual from './utils/deepEqual';
|
|
6
|
+
import type {
|
|
7
|
+
Control,
|
|
8
|
+
DeepPartialSkipArrayKey,
|
|
9
|
+
FieldPath,
|
|
10
|
+
FieldPathValue,
|
|
11
|
+
FieldPathValues,
|
|
12
|
+
FieldValues,
|
|
13
|
+
InternalFieldName,
|
|
14
|
+
UseWatchProps,
|
|
15
|
+
} from './types';
|
|
16
|
+
import { useFormControlContext } from './useFormControlContext';
|
|
17
|
+
import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Subscribe to the entire form values change and re-render at the hook level.
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
*
|
|
24
|
+
* [API](https://react-hook-form.com/docs/usewatch) • [Demo](https://codesandbox.io/s/react-hook-form-v7-ts-usewatch-h9i5e)
|
|
25
|
+
*
|
|
26
|
+
* @param props - defaultValue, disable subscription and match exact name.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* const { control } = useForm();
|
|
31
|
+
* const values = useWatch({
|
|
32
|
+
* control,
|
|
33
|
+
* defaultValue: {
|
|
34
|
+
* name: "data"
|
|
35
|
+
* },
|
|
36
|
+
* exact: false,
|
|
37
|
+
* })
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function useWatch<
|
|
41
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
42
|
+
TTransformedValues = TFieldValues,
|
|
43
|
+
>(props: {
|
|
44
|
+
name?: undefined;
|
|
45
|
+
defaultValue?: DeepPartialSkipArrayKey<TFieldValues>;
|
|
46
|
+
control?: Control<TFieldValues, any, TTransformedValues>;
|
|
47
|
+
disabled?: boolean;
|
|
48
|
+
exact?: boolean;
|
|
49
|
+
compute?: undefined;
|
|
50
|
+
}): DeepPartialSkipArrayKey<TFieldValues>;
|
|
51
|
+
/**
|
|
52
|
+
* Custom hook to subscribe to field change and isolate re-rendering at the component level.
|
|
53
|
+
*
|
|
54
|
+
* @remarks
|
|
55
|
+
*
|
|
56
|
+
* [API](https://react-hook-form.com/docs/usewatch) • [Demo](https://codesandbox.io/s/react-hook-form-v7-ts-usewatch-h9i5e)
|
|
57
|
+
*
|
|
58
|
+
* @param props - defaultValue, disable subscription and match exact name.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```tsx
|
|
62
|
+
* const { control } = useForm();
|
|
63
|
+
* const values = useWatch({
|
|
64
|
+
* control,
|
|
65
|
+
* name: "fieldA",
|
|
66
|
+
* defaultValue: "default value",
|
|
67
|
+
* exact: false,
|
|
68
|
+
* })
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export function useWatch<
|
|
72
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
73
|
+
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
74
|
+
TTransformedValues = TFieldValues,
|
|
75
|
+
>(props: {
|
|
76
|
+
name: TFieldName;
|
|
77
|
+
defaultValue?: FieldPathValue<TFieldValues, TFieldName>;
|
|
78
|
+
control?: Control<TFieldValues, any, TTransformedValues>;
|
|
79
|
+
disabled?: boolean;
|
|
80
|
+
exact?: boolean;
|
|
81
|
+
compute?: undefined;
|
|
82
|
+
}): FieldPathValue<TFieldValues, TFieldName>;
|
|
83
|
+
/**
|
|
84
|
+
* Custom hook to subscribe to field change and compute function to produce state update
|
|
85
|
+
*
|
|
86
|
+
* @remarks
|
|
87
|
+
*
|
|
88
|
+
* [API](https://react-hook-form.com/docs/usewatch)
|
|
89
|
+
*
|
|
90
|
+
* @param props - defaultValue, disable subscription and match exact name.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```tsx
|
|
94
|
+
* const { control } = useForm();
|
|
95
|
+
* const values = useWatch({
|
|
96
|
+
* control,
|
|
97
|
+
* compute: (formValues) => formValues.fieldA
|
|
98
|
+
* })
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export function useWatch<
|
|
102
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
103
|
+
TTransformedValues = TFieldValues,
|
|
104
|
+
TComputeValue = unknown,
|
|
105
|
+
>(props: {
|
|
106
|
+
name?: undefined;
|
|
107
|
+
defaultValue?: DeepPartialSkipArrayKey<TFieldValues>;
|
|
108
|
+
control?: Control<TFieldValues, any, TTransformedValues>;
|
|
109
|
+
disabled?: boolean;
|
|
110
|
+
exact?: boolean;
|
|
111
|
+
compute: (formValues: TFieldValues) => TComputeValue;
|
|
112
|
+
}): TComputeValue;
|
|
113
|
+
/**
|
|
114
|
+
* Custom hook to subscribe to field change and compute function to produce state update
|
|
115
|
+
*
|
|
116
|
+
* @remarks
|
|
117
|
+
*
|
|
118
|
+
* [API](https://react-hook-form.com/docs/usewatch)
|
|
119
|
+
*
|
|
120
|
+
* @param props - defaultValue, disable subscription and match exact name.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```tsx
|
|
124
|
+
* const { control } = useForm();
|
|
125
|
+
* const values = useWatch({
|
|
126
|
+
* control,
|
|
127
|
+
* name: "fieldA",
|
|
128
|
+
* defaultValue: "default value",
|
|
129
|
+
* exact: false,
|
|
130
|
+
* compute: (fieldValue) => fieldValue === "data" ? fieldValue : null,
|
|
131
|
+
* })
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export function useWatch<
|
|
135
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
136
|
+
TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
137
|
+
TTransformedValues = TFieldValues,
|
|
138
|
+
TComputeValue = unknown,
|
|
139
|
+
>(props: {
|
|
140
|
+
name: TFieldName;
|
|
141
|
+
defaultValue?: FieldPathValue<TFieldValues, TFieldName>;
|
|
142
|
+
control?: Control<TFieldValues, any, TTransformedValues>;
|
|
143
|
+
disabled?: boolean;
|
|
144
|
+
exact?: boolean;
|
|
145
|
+
compute: (fieldValue: FieldPathValue<TFieldValues, TFieldName>) => TComputeValue;
|
|
146
|
+
}): TComputeValue;
|
|
147
|
+
/**
|
|
148
|
+
* Custom hook to subscribe to field change and isolate re-rendering at the component level.
|
|
149
|
+
*
|
|
150
|
+
* @remarks
|
|
151
|
+
*
|
|
152
|
+
* [API](https://react-hook-form.com/docs/usewatch) • [Demo](https://codesandbox.io/s/react-hook-form-v7-ts-usewatch-h9i5e)
|
|
153
|
+
*
|
|
154
|
+
* @param props - defaultValue, disable subscription and match exact name.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```tsx
|
|
158
|
+
* const { control } = useForm();
|
|
159
|
+
* const values = useWatch({
|
|
160
|
+
* control,
|
|
161
|
+
* name: ["fieldA", "fieldB"],
|
|
162
|
+
* defaultValue: {
|
|
163
|
+
* fieldA: "data",
|
|
164
|
+
* fieldB: "data"
|
|
165
|
+
* },
|
|
166
|
+
* exact: false,
|
|
167
|
+
* })
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
export function useWatch<
|
|
171
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
172
|
+
TFieldNames extends readonly FieldPath<TFieldValues>[] = readonly FieldPath<TFieldValues>[],
|
|
173
|
+
TTransformedValues = TFieldValues,
|
|
174
|
+
>(props: {
|
|
175
|
+
name: readonly [...TFieldNames];
|
|
176
|
+
defaultValue?: DeepPartialSkipArrayKey<TFieldValues>;
|
|
177
|
+
control?: Control<TFieldValues, any, TTransformedValues>;
|
|
178
|
+
disabled?: boolean;
|
|
179
|
+
exact?: boolean;
|
|
180
|
+
compute?: undefined;
|
|
181
|
+
}): FieldPathValues<TFieldValues, TFieldNames>;
|
|
182
|
+
/**
|
|
183
|
+
* Custom hook to subscribe to field change and compute function to produce state update
|
|
184
|
+
*
|
|
185
|
+
* @remarks
|
|
186
|
+
*
|
|
187
|
+
* [API](https://react-hook-form.com/docs/usewatch)
|
|
188
|
+
*
|
|
189
|
+
* @param props - defaultValue, disable subscription and match exact name.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```tsx
|
|
193
|
+
* const { control } = useForm();
|
|
194
|
+
* const values = useWatch({
|
|
195
|
+
* control,
|
|
196
|
+
* name: ["fieldA", "fieldB"],
|
|
197
|
+
* defaultValue: {
|
|
198
|
+
* fieldA: "data",
|
|
199
|
+
* fieldB: 0
|
|
200
|
+
* },
|
|
201
|
+
* compute: ([fieldAValue, fieldBValue]) => fieldB === 2 ? fieldA : null,
|
|
202
|
+
* exact: false,
|
|
203
|
+
* })
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
export function useWatch<
|
|
207
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
208
|
+
TFieldNames extends readonly FieldPath<TFieldValues>[] = readonly FieldPath<TFieldValues>[],
|
|
209
|
+
TTransformedValues = TFieldValues,
|
|
210
|
+
TComputeValue = unknown,
|
|
211
|
+
>(props: {
|
|
212
|
+
name: readonly [...TFieldNames];
|
|
213
|
+
defaultValue?: DeepPartialSkipArrayKey<TFieldValues>;
|
|
214
|
+
control?: Control<TFieldValues, any, TTransformedValues>;
|
|
215
|
+
disabled?: boolean;
|
|
216
|
+
exact?: boolean;
|
|
217
|
+
compute: (fieldValue: FieldPathValues<TFieldValues, TFieldNames>) => TComputeValue;
|
|
218
|
+
}): TComputeValue;
|
|
219
|
+
/**
|
|
220
|
+
* Custom hook to subscribe to field change and isolate re-rendering at the component level.
|
|
221
|
+
*
|
|
222
|
+
* @remarks
|
|
223
|
+
*
|
|
224
|
+
* [API](https://react-hook-form.com/docs/usewatch) • [Demo](https://codesandbox.io/s/react-hook-form-v7-ts-usewatch-h9i5e)
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```tsx
|
|
228
|
+
* // can skip passing down the control into useWatch if the form is wrapped with the FormProvider
|
|
229
|
+
* const values = useWatch()
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
export function useWatch<
|
|
233
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
234
|
+
>(): DeepPartialSkipArrayKey<TFieldValues>;
|
|
235
|
+
/**
|
|
236
|
+
* Custom hook to subscribe to field change and isolate re-rendering at the component level.
|
|
237
|
+
*
|
|
238
|
+
* @remarks
|
|
239
|
+
*
|
|
240
|
+
* [API](https://react-hook-form.com/docs/usewatch) • [Demo](https://codesandbox.io/s/react-hook-form-v7-ts-usewatch-h9i5e)
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```tsx
|
|
244
|
+
* const { control } = useForm();
|
|
245
|
+
* const values = useWatch({
|
|
246
|
+
* name: "fieldName"
|
|
247
|
+
* control,
|
|
248
|
+
* })
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
export function useWatch<TFieldValues extends FieldValues>(props?: UseWatchProps<TFieldValues>) {
|
|
252
|
+
const formControl = useFormControlContext<TFieldValues>();
|
|
253
|
+
const { control = formControl, name, defaultValue, disabled, exact, compute } = props || {};
|
|
254
|
+
const _defaultValue = useRef(defaultValue);
|
|
255
|
+
const _compute = useRef(compute);
|
|
256
|
+
const _computeFormValues = useRef<undefined | unknown>(undefined);
|
|
257
|
+
|
|
258
|
+
const _prevControl = useRef(control);
|
|
259
|
+
const _prevName = useRef(name);
|
|
260
|
+
|
|
261
|
+
_compute.current = compute;
|
|
262
|
+
|
|
263
|
+
const [value, updateValue] = useState(() => {
|
|
264
|
+
const defaultValue = control._getWatch(
|
|
265
|
+
name as InternalFieldName,
|
|
266
|
+
_defaultValue.current as DeepPartialSkipArrayKey<TFieldValues>,
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
return _compute.current ? _compute.current(defaultValue) : defaultValue;
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const getCurrentOutput = useCallback(
|
|
273
|
+
(values?: TFieldValues) => {
|
|
274
|
+
const formValues = generateWatchOutput(
|
|
275
|
+
name as InternalFieldName | InternalFieldName[],
|
|
276
|
+
control._names,
|
|
277
|
+
values || control._formValues,
|
|
278
|
+
false,
|
|
279
|
+
_defaultValue.current,
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
return _compute.current ? _compute.current(formValues) : formValues;
|
|
283
|
+
},
|
|
284
|
+
[control._formValues, control._names, name],
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
const refreshValue = useCallback(
|
|
288
|
+
(values?: TFieldValues) => {
|
|
289
|
+
if (!disabled) {
|
|
290
|
+
const formValues = generateWatchOutput(
|
|
291
|
+
name as InternalFieldName | InternalFieldName[],
|
|
292
|
+
control._names,
|
|
293
|
+
values || control._formValues,
|
|
294
|
+
false,
|
|
295
|
+
_defaultValue.current,
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
if (_compute.current) {
|
|
299
|
+
const computedFormValues = _compute.current(formValues);
|
|
300
|
+
|
|
301
|
+
if (!deepEqual(computedFormValues, _computeFormValues.current)) {
|
|
302
|
+
updateValue(computedFormValues);
|
|
303
|
+
_computeFormValues.current = computedFormValues;
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
updateValue(formValues);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
[control._formValues, control._names, disabled, name],
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
useIsomorphicLayoutEffect(() => {
|
|
314
|
+
if (_prevControl.current !== control || !deepEqual(_prevName.current, name)) {
|
|
315
|
+
_prevControl.current = control;
|
|
316
|
+
_prevName.current = name;
|
|
317
|
+
refreshValue();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return control._subscribe({
|
|
321
|
+
name,
|
|
322
|
+
formState: {
|
|
323
|
+
values: true,
|
|
324
|
+
},
|
|
325
|
+
exact,
|
|
326
|
+
callback: (formState) => {
|
|
327
|
+
refreshValue(formState.values);
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
}, [control, exact, name, refreshValue]);
|
|
331
|
+
|
|
332
|
+
useEffect(() => control._removeUnmounted());
|
|
333
|
+
|
|
334
|
+
// If name or control changed for this render, synchronously reflect the
|
|
335
|
+
// latest value so callers (like useController) see the correct value
|
|
336
|
+
// immediately on the same render.
|
|
337
|
+
|
|
338
|
+
// Optimize: Check control reference first before expensive deepEqual
|
|
339
|
+
const controlChanged = _prevControl.current !== control;
|
|
340
|
+
const prevName = _prevName.current;
|
|
341
|
+
|
|
342
|
+
// Cache the computed output to avoid duplicate calls within the same render
|
|
343
|
+
// We include shouldReturnImmediate in deps to ensure proper recomputation
|
|
344
|
+
const computedOutput = useMemo(() => {
|
|
345
|
+
if (disabled) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const nameChanged = !controlChanged && !deepEqual(prevName, name);
|
|
350
|
+
const shouldReturnImmediate = controlChanged || nameChanged;
|
|
351
|
+
|
|
352
|
+
return shouldReturnImmediate ? getCurrentOutput() : null;
|
|
353
|
+
}, [disabled, controlChanged, name, prevName, getCurrentOutput]);
|
|
354
|
+
|
|
355
|
+
return computedOutput !== null ? computedOutput : value;
|
|
356
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Vendored from react-hook-form@7.81.0 src/utils/cloneObject.ts (octane port).
|
|
2
|
+
import isObject from './isObject';
|
|
3
|
+
import isPlainObject from './isPlainObject';
|
|
4
|
+
import isWeb from './isWeb';
|
|
5
|
+
|
|
6
|
+
export default function cloneObject<T>(data: T): T {
|
|
7
|
+
if (data instanceof Date) {
|
|
8
|
+
return new Date(data) as any;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const isFileListInstance = typeof FileList !== 'undefined' && data instanceof FileList;
|
|
12
|
+
|
|
13
|
+
if (isWeb && (data instanceof Blob || isFileListInstance)) {
|
|
14
|
+
return data;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const isArray = Array.isArray(data);
|
|
18
|
+
|
|
19
|
+
if (!isArray && !(isObject(data) && isPlainObject(data))) {
|
|
20
|
+
return data;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const copy = isArray ? [] : Object.create(Object.getPrototypeOf(data));
|
|
24
|
+
|
|
25
|
+
for (const key in data) {
|
|
26
|
+
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
27
|
+
copy[key] = cloneObject(data[key]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return copy;
|
|
32
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Vendored from react-hook-form@7.81.0 src/utils/createSubject.ts (octane port).
|
|
2
|
+
import type { Noop } from '../types';
|
|
3
|
+
|
|
4
|
+
export type Observer<T> = {
|
|
5
|
+
next: (value: T) => void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type Subscription = {
|
|
9
|
+
unsubscribe: Noop;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type Subject<T> = {
|
|
13
|
+
readonly observers: Observer<T>[];
|
|
14
|
+
subscribe: (value: Observer<T>) => Subscription;
|
|
15
|
+
unsubscribe: Noop;
|
|
16
|
+
} & Observer<T>;
|
|
17
|
+
|
|
18
|
+
export default <T>(): Subject<T> => {
|
|
19
|
+
let _observers: Observer<T>[] = [];
|
|
20
|
+
|
|
21
|
+
const next = (value: T) => {
|
|
22
|
+
for (const observer of _observers) {
|
|
23
|
+
observer.next && observer.next(value);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const subscribe = (observer: Observer<T>): Subscription => {
|
|
28
|
+
_observers.push(observer);
|
|
29
|
+
return {
|
|
30
|
+
unsubscribe: () => {
|
|
31
|
+
_observers = _observers.filter((o) => o !== observer);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const unsubscribe = () => {
|
|
37
|
+
_observers = [];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
get observers() {
|
|
42
|
+
return _observers;
|
|
43
|
+
},
|
|
44
|
+
next,
|
|
45
|
+
subscribe,
|
|
46
|
+
unsubscribe,
|
|
47
|
+
};
|
|
48
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Vendored from react-hook-form@7.81.0 src/utils/deepEqual.ts (octane port).
|
|
2
|
+
import isDateObject from './isDateObject';
|
|
3
|
+
import isObject from './isObject';
|
|
4
|
+
import isPlainObject from './isPlainObject';
|
|
5
|
+
import isPrimitive from './isPrimitive';
|
|
6
|
+
|
|
7
|
+
const isEmptyObjectWithCustomPrototype = (object: object, keys: string[]) =>
|
|
8
|
+
keys.length === 0 && !Array.isArray(object) && !isPlainObject(object);
|
|
9
|
+
|
|
10
|
+
export default function deepEqual(
|
|
11
|
+
object1: any,
|
|
12
|
+
object2: any,
|
|
13
|
+
visited = new WeakMap<object, WeakSet<object>>(),
|
|
14
|
+
) {
|
|
15
|
+
if (object1 === object2) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (isPrimitive(object1) || isPrimitive(object2)) {
|
|
20
|
+
return Object.is(object1, object2);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (isDateObject(object1) && isDateObject(object2)) {
|
|
24
|
+
return Object.is(object1.getTime(), object2.getTime());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const keys1 = Object.keys(object1);
|
|
28
|
+
const keys2 = Object.keys(object2);
|
|
29
|
+
|
|
30
|
+
if (keys1.length !== keys2.length) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (
|
|
35
|
+
isEmptyObjectWithCustomPrototype(object1, keys1) ||
|
|
36
|
+
isEmptyObjectWithCustomPrototype(object2, keys2)
|
|
37
|
+
) {
|
|
38
|
+
return Object.is(object1, object2);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!keys1.length && Array.isArray(object1) !== Array.isArray(object2)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const visitedPairs = visited.get(object1);
|
|
46
|
+
|
|
47
|
+
if (visitedPairs && visitedPairs.has(object2)) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (visitedPairs) {
|
|
52
|
+
visitedPairs.add(object2);
|
|
53
|
+
} else {
|
|
54
|
+
const ws = new WeakSet();
|
|
55
|
+
ws.add(object2);
|
|
56
|
+
visited.set(object1, ws);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const key of keys1) {
|
|
60
|
+
const val1 = object1[key];
|
|
61
|
+
|
|
62
|
+
if (!(key in object2)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (key !== 'ref') {
|
|
67
|
+
const val2 = object2[key];
|
|
68
|
+
|
|
69
|
+
if (
|
|
70
|
+
(isDateObject(val1) && isDateObject(val2)) ||
|
|
71
|
+
((isObject(val1) || Array.isArray(val1)) && (isObject(val2) || Array.isArray(val2)))
|
|
72
|
+
? !deepEqual(val1, val2, visited)
|
|
73
|
+
: !Object.is(val1, val2)
|
|
74
|
+
) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return true;
|
|
81
|
+
}
|