remix-validated-form 4.1.0-beta.1 → 4.1.1
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 +2 -2
- package/browser/ValidatedForm.d.ts +2 -2
- package/browser/ValidatedForm.js +53 -23
- package/browser/hooks.js +9 -3
- package/browser/internal/MultiValueMap.js +3 -3
- package/browser/internal/hooks.d.ts +0 -1
- package/browser/internal/hooks.js +0 -4
- package/browser/internal/logic/getCheckboxChecked copy.d.ts +1 -0
- package/browser/internal/logic/getCheckboxChecked copy.js +9 -0
- package/browser/internal/logic/getCheckboxChecked.d.ts +1 -0
- package/browser/internal/logic/getCheckboxChecked.js +9 -0
- package/browser/internal/logic/getRadioChecked.d.ts +1 -0
- package/browser/internal/logic/getRadioChecked.js +5 -0
- package/browser/internal/logic/setFieldValue.d.ts +1 -0
- package/browser/internal/logic/setFieldValue.js +40 -0
- package/browser/internal/util.d.ts +1 -0
- package/browser/internal/util.js +12 -1
- package/build/ValidatedForm.d.ts +2 -2
- package/build/ValidatedForm.js +52 -22
- package/build/hooks.js +8 -2
- package/build/internal/MultiValueMap.js +2 -2
- package/build/internal/hooks.d.ts +0 -1
- package/build/internal/hooks.js +1 -6
- package/build/internal/logic/getCheckboxChecked.d.ts +1 -0
- package/build/internal/logic/getCheckboxChecked.js +13 -0
- package/build/internal/logic/getRadioChecked.d.ts +1 -0
- package/build/internal/logic/getRadioChecked.js +9 -0
- package/build/internal/logic/setFieldValue.d.ts +1 -0
- package/build/internal/logic/setFieldValue.js +47 -0
- package/build/internal/util.d.ts +1 -0
- package/build/internal/util.js +13 -1
- package/package.json +1 -1
- package/src/ValidatedForm.tsx +64 -29
- package/src/hooks.ts +8 -5
- package/src/internal/MultiValueMap.ts +3 -3
- package/src/internal/hooks.ts +0 -9
- package/src/internal/util.ts +13 -1
package/.turbo/turbo-build.log
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
[2K[1G[2m$ npm run build:browser && npm run build:main[22m
|
2
2
|
|
3
|
-
> remix-validated-form@4.0
|
3
|
+
> remix-validated-form@4.1.0 build:browser
|
4
4
|
> tsc --module ESNext --outDir ./browser
|
5
5
|
|
6
6
|
|
7
|
-
> remix-validated-form@4.0
|
7
|
+
> remix-validated-form@4.1.0 build:main
|
8
8
|
> tsc --module CommonJS --outDir ./build
|
9
9
|
|
@@ -10,7 +10,7 @@ export declare type FormProps<DataType> = {
|
|
10
10
|
* A submit callback that gets called when the form is submitted
|
11
11
|
* after all validations have been run.
|
12
12
|
*/
|
13
|
-
onSubmit?: (data: DataType, event: React.FormEvent<HTMLFormElement>) => Promise<void>;
|
13
|
+
onSubmit?: (data: DataType, event: React.FormEvent<HTMLFormElement>) => void | Promise<void>;
|
14
14
|
/**
|
15
15
|
* Allows you to provide a `fetcher` from remix's `useFetcher` hook.
|
16
16
|
* The form will use the fetcher for loading states, action data, etc
|
@@ -47,4 +47,4 @@ export declare type FormProps<DataType> = {
|
|
47
47
|
/**
|
48
48
|
* The primary form component of `remix-validated-form`.
|
49
49
|
*/
|
50
|
-
export declare function ValidatedForm<DataType>({ validator, onSubmit, children, fetcher, action, defaultValues:
|
50
|
+
export declare function ValidatedForm<DataType>({ validator, onSubmit, children, fetcher, action, defaultValues: unMemoizedDefaults, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, method, replace, id, ...rest }: FormProps<DataType>): JSX.Element;
|
package/browser/ValidatedForm.js
CHANGED
@@ -10,7 +10,7 @@ import { useDefaultValuesFromLoader, useErrorResponseForForm, useFormUpdateAtom,
|
|
10
10
|
import { useMultiValueMap } from "./internal/MultiValueMap";
|
11
11
|
import { addErrorAtom, clearErrorAtom, endSubmitAtom, formRegistry, resetAtom, setFieldErrorsAtom, startSubmitAtom, syncFormContextAtom, } from "./internal/state";
|
12
12
|
import { useSubmitComplete } from "./internal/submissionCallbacks";
|
13
|
-
import { mergeRefs, useIsomorphicLayoutEffect as useLayoutEffect, } from "./internal/util";
|
13
|
+
import { mergeRefs, useDeepEqualsMemo, useIsomorphicLayoutEffect as useLayoutEffect, } from "./internal/util";
|
14
14
|
const getDataFromForm = (el) => new FormData(el);
|
15
15
|
function nonNull(value) {
|
16
16
|
return value !== null;
|
@@ -75,13 +75,35 @@ const FormResetter = ({ resetAfterSubmit, formRef, }) => {
|
|
75
75
|
});
|
76
76
|
return null;
|
77
77
|
};
|
78
|
+
function formEventProxy(event) {
|
79
|
+
let defaultPrevented = false;
|
80
|
+
return new Proxy(event, {
|
81
|
+
get: (target, prop) => {
|
82
|
+
if (prop === "preventDefault") {
|
83
|
+
return () => {
|
84
|
+
defaultPrevented = true;
|
85
|
+
};
|
86
|
+
}
|
87
|
+
if (prop === "defaultPrevented") {
|
88
|
+
return defaultPrevented;
|
89
|
+
}
|
90
|
+
return target[prop];
|
91
|
+
},
|
92
|
+
});
|
93
|
+
}
|
94
|
+
const useFormAtom = (formId) => {
|
95
|
+
const formAtom = formRegistry(formId);
|
96
|
+
useEffect(() => () => formRegistry.remove(formId), [formId]);
|
97
|
+
return formAtom;
|
98
|
+
};
|
78
99
|
/**
|
79
100
|
* The primary form component of `remix-validated-form`.
|
80
101
|
*/
|
81
|
-
export function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues:
|
102
|
+
export function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues: unMemoizedDefaults, formRef: formRefProp, onReset, subaction, resetAfterSubmit = false, disableFocusOnError, method, replace, id, ...rest }) {
|
82
103
|
var _a;
|
83
104
|
const formId = useFormId(id);
|
84
|
-
const formAtom =
|
105
|
+
const formAtom = useFormAtom(formId);
|
106
|
+
const providedDefaultValues = useDeepEqualsMemo(unMemoizedDefaults);
|
85
107
|
const contextValue = useMemo(() => ({
|
86
108
|
formId,
|
87
109
|
action,
|
@@ -170,28 +192,36 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
170
192
|
window.removeEventListener("click", handleClick);
|
171
193
|
};
|
172
194
|
}, []);
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
focusFirstInvalidInput(result.error.fieldErrors, customFocusHandlers(), formRef.current);
|
182
|
-
}
|
195
|
+
const handleSubmit = async (e) => {
|
196
|
+
startSubmit({ formAtom });
|
197
|
+
const result = await validator.validate(getDataFromForm(e.currentTarget));
|
198
|
+
if (result.error) {
|
199
|
+
endSubmit({ formAtom });
|
200
|
+
setFieldErrors({ fieldErrors: result.error.fieldErrors, formAtom });
|
201
|
+
if (!disableFocusOnError) {
|
202
|
+
focusFirstInvalidInput(result.error.fieldErrors, customFocusHandlers(), formRef.current);
|
183
203
|
}
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
replace,
|
192
|
-
});
|
193
|
-
clickedButtonRef.current = null;
|
204
|
+
}
|
205
|
+
else {
|
206
|
+
const eventProxy = formEventProxy(e);
|
207
|
+
await (onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(result.data, eventProxy));
|
208
|
+
if (eventProxy.defaultPrevented) {
|
209
|
+
endSubmit({ formAtom });
|
210
|
+
return;
|
194
211
|
}
|
212
|
+
if (fetcher)
|
213
|
+
fetcher.submit(clickedButtonRef.current || e.currentTarget);
|
214
|
+
else
|
215
|
+
submit(clickedButtonRef.current || e.currentTarget, {
|
216
|
+
method,
|
217
|
+
replace,
|
218
|
+
});
|
219
|
+
clickedButtonRef.current = null;
|
220
|
+
}
|
221
|
+
};
|
222
|
+
return (_jsx(Form, { ref: mergeRefs([formRef, formRefProp]), ...rest, id: id, action: action, method: method, replace: replace, onSubmit: (e) => {
|
223
|
+
e.preventDefault();
|
224
|
+
handleSubmit(e);
|
195
225
|
}, onReset: (event) => {
|
196
226
|
onReset === null || onReset === void 0 ? void 0 : onReset(event);
|
197
227
|
if (event.defaultPrevented)
|
package/browser/hooks.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { useEffect, useMemo } from "react";
|
2
2
|
import { createGetInputProps, } from "./internal/getInputProps";
|
3
|
-
import {
|
3
|
+
import { useInternalFormContext, useFieldTouched, useFieldError, useFieldDefaultValue, useContextSelectAtom, useClearError, useSetTouched, } from "./internal/hooks";
|
4
4
|
import { hasBeenSubmittedAtom, isSubmittingAtom, isValidAtom, registerReceiveFocusAtom, validateFieldAtom, } from "./internal/state";
|
5
5
|
/**
|
6
6
|
* Returns whether or not the parent form is currently being submitted.
|
@@ -9,13 +9,19 @@ import { hasBeenSubmittedAtom, isSubmittingAtom, isValidAtom, registerReceiveFoc
|
|
9
9
|
*
|
10
10
|
* @param formId
|
11
11
|
*/
|
12
|
-
export const useIsSubmitting = (formId) =>
|
12
|
+
export const useIsSubmitting = (formId) => {
|
13
|
+
const formContext = useInternalFormContext(formId, "useIsSubmitting");
|
14
|
+
return useContextSelectAtom(formContext.formId, isSubmittingAtom);
|
15
|
+
};
|
13
16
|
/**
|
14
17
|
* Returns whether or not the current form is valid.
|
15
18
|
*
|
16
19
|
* @param formId the id of the form. Only necessary if being used outside a ValidatedForm.
|
17
20
|
*/
|
18
|
-
export const useIsValid = (formId) =>
|
21
|
+
export const useIsValid = (formId) => {
|
22
|
+
const formContext = useInternalFormContext(formId, "useIsValid");
|
23
|
+
return useContextSelectAtom(formContext.formId, isValidAtom);
|
24
|
+
};
|
19
25
|
/**
|
20
26
|
* Provides the data and helpers necessary to set up a field.
|
21
27
|
*/
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { useRef } from "react";
|
1
|
+
import { useCallback, useRef } from "react";
|
2
2
|
export class MultiValueMap {
|
3
3
|
constructor() {
|
4
4
|
this.dict = new Map();
|
@@ -30,10 +30,10 @@ export class MultiValueMap {
|
|
30
30
|
}
|
31
31
|
export const useMultiValueMap = () => {
|
32
32
|
const ref = useRef(null);
|
33
|
-
return () => {
|
33
|
+
return useCallback(() => {
|
34
34
|
if (ref.current)
|
35
35
|
return ref.current;
|
36
36
|
ref.current = new MultiValueMap();
|
37
37
|
return ref.current;
|
38
|
-
};
|
38
|
+
}, []);
|
39
39
|
};
|
@@ -7,7 +7,6 @@ declare type FormSelectorAtomCreator<T> = (formState: FormAtom) => Atom<T>;
|
|
7
7
|
declare const USE_HYDRATED_STATE: unique symbol;
|
8
8
|
export declare const useInternalFormContext: (formId?: string | symbol | undefined, hookName?: string | undefined) => InternalFormContextValue;
|
9
9
|
export declare const useContextSelectAtom: <T>(formId: string | symbol, selectorAtomCreator: FormSelectorAtomCreator<T>) => T extends Promise<infer V> ? V : T;
|
10
|
-
export declare const useUnknownFormContextSelectAtom: <T>(formId: string | symbol | undefined, selectorAtomCreator: FormSelectorAtomCreator<T>, hookName: string) => T extends Promise<infer V> ? V : T;
|
11
10
|
export declare const useHydratableSelector: <T, U>({ formId }: InternalFormContextValue, atomCreator: FormSelectorAtomCreator<T>, dataToUse: U | typeof USE_HYDRATED_STATE, selector?: (data: U) => T) => T | (T extends Promise<infer V> ? V : T);
|
12
11
|
export declare function useErrorResponseForForm({ fetcher, subaction, formId, }: InternalFormContextValue): ValidationErrorResponseData | null;
|
13
12
|
export declare const useFieldErrorsForForm: (context: InternalFormContextValue) => import("..").FieldErrors | typeof USE_HYDRATED_STATE | undefined;
|
@@ -20,10 +20,6 @@ export const useContextSelectAtom = (formId, selectorAtomCreator) => {
|
|
20
20
|
const selectorAtom = useMemo(() => selectorAtomCreator(formAtom), [formAtom, selectorAtomCreator]);
|
21
21
|
return useAtomValue(selectorAtom, ATOM_SCOPE);
|
22
22
|
};
|
23
|
-
export const useUnknownFormContextSelectAtom = (formId, selectorAtomCreator, hookName) => {
|
24
|
-
const formContext = useInternalFormContext(formId, hookName);
|
25
|
-
return useContextSelectAtom(formContext.formId, selectorAtomCreator);
|
26
|
-
};
|
27
23
|
export const useHydratableSelector = ({ formId }, atomCreator, dataToUse, selector = identity) => {
|
28
24
|
const dataFromState = useContextSelectAtom(formId, atomCreator);
|
29
25
|
return dataToUse === USE_HYDRATED_STATE ? dataFromState : selector(dataToUse);
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const getCheckboxChecked: (checkboxValue: string | undefined, newValue: unknown) => boolean | undefined;
|
@@ -0,0 +1,9 @@
|
|
1
|
+
export const getCheckboxChecked = (checkboxValue = "on", newValue) => {
|
2
|
+
if (Array.isArray(newValue))
|
3
|
+
return newValue.includes(checkboxValue);
|
4
|
+
if (typeof newValue === "boolean")
|
5
|
+
return newValue;
|
6
|
+
if (typeof newValue === "string")
|
7
|
+
return newValue === checkboxValue;
|
8
|
+
return undefined;
|
9
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const getCheckboxChecked: (checkboxValue: string | undefined, newValue: unknown) => boolean | undefined;
|
@@ -0,0 +1,9 @@
|
|
1
|
+
export const getCheckboxChecked = (checkboxValue = "on", newValue) => {
|
2
|
+
if (Array.isArray(newValue))
|
3
|
+
return newValue.includes(checkboxValue);
|
4
|
+
if (typeof newValue === "boolean")
|
5
|
+
return newValue;
|
6
|
+
if (typeof newValue === "string")
|
7
|
+
return newValue === checkboxValue;
|
8
|
+
return undefined;
|
9
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const getRadioChecked: (radioValue: string | undefined, newValue: unknown) => boolean | undefined;
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const setFieldValue: (formElement: HTMLFormElement, name: string, value: unknown) => void;
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import invariant from "tiny-invariant";
|
2
|
+
import { getCheckboxChecked } from "./getCheckboxChecked";
|
3
|
+
import { getRadioChecked } from "./getRadioChecked";
|
4
|
+
const setElementValue = (element, value, name) => {
|
5
|
+
if (element instanceof HTMLSelectElement && element.multiple) {
|
6
|
+
invariant(Array.isArray(value), "Must specify an array to set the value for a multi-select");
|
7
|
+
for (const option of element.options) {
|
8
|
+
option.selected = value.includes(option.value);
|
9
|
+
}
|
10
|
+
return;
|
11
|
+
}
|
12
|
+
if (element instanceof HTMLInputElement && element.type === "checkbox") {
|
13
|
+
const newChecked = getCheckboxChecked(element.value, value);
|
14
|
+
invariant(newChecked !== undefined, `Unable to determine if checkbox should be checked. Provided value was ${value} for checkbox ${name}.`);
|
15
|
+
element.checked = newChecked;
|
16
|
+
return;
|
17
|
+
}
|
18
|
+
if (element instanceof HTMLInputElement && element.type === "radio") {
|
19
|
+
const newChecked = getRadioChecked(element.value, value);
|
20
|
+
invariant(newChecked !== undefined, `Unable to determine if radio should be checked. Provided value was ${value} for radio ${name}.`);
|
21
|
+
element.checked = newChecked;
|
22
|
+
return;
|
23
|
+
}
|
24
|
+
invariant(typeof value === "string", `Invalid value for field "${name}" which is an ${element.constructor.name}. Expected string but received ${typeof value}`);
|
25
|
+
const input = element;
|
26
|
+
input.value = value;
|
27
|
+
};
|
28
|
+
export const setFieldValue = (formElement, name, value) => {
|
29
|
+
const controlElement = formElement.elements.namedItem(name);
|
30
|
+
if (!controlElement)
|
31
|
+
return;
|
32
|
+
if (controlElement instanceof RadioNodeList) {
|
33
|
+
for (const element of controlElement) {
|
34
|
+
setElementValue(element, value, name);
|
35
|
+
}
|
36
|
+
}
|
37
|
+
else {
|
38
|
+
setElementValue(controlElement, value, name);
|
39
|
+
}
|
40
|
+
};
|
@@ -2,3 +2,4 @@ import type React from "react";
|
|
2
2
|
export declare const omit: (obj: any, ...keys: string[]) => any;
|
3
3
|
export declare const mergeRefs: <T = any>(refs: (React.MutableRefObject<T> | React.LegacyRef<T> | undefined)[]) => (instance: T | null) => void;
|
4
4
|
export declare const useIsomorphicLayoutEffect: typeof React.useEffect;
|
5
|
+
export declare const useDeepEqualsMemo: <T>(item: T) => T;
|
package/browser/internal/util.js
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
import {
|
1
|
+
import { isEqual } from "lodash";
|
2
|
+
import { useEffect, useLayoutEffect, useRef } from "react";
|
2
3
|
export const omit = (obj, ...keys) => {
|
3
4
|
const result = { ...obj };
|
4
5
|
for (const key of keys) {
|
@@ -19,3 +20,13 @@ export const mergeRefs = (refs) => {
|
|
19
20
|
};
|
20
21
|
};
|
21
22
|
export const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
23
|
+
export const useDeepEqualsMemo = (item) => {
|
24
|
+
const ref = useRef(item);
|
25
|
+
const areEqual = ref.current === item || isEqual(ref.current, item);
|
26
|
+
useEffect(() => {
|
27
|
+
if (!areEqual) {
|
28
|
+
ref.current = item;
|
29
|
+
}
|
30
|
+
});
|
31
|
+
return areEqual ? ref.current : item;
|
32
|
+
};
|
package/build/ValidatedForm.d.ts
CHANGED
@@ -10,7 +10,7 @@ export declare type FormProps<DataType> = {
|
|
10
10
|
* A submit callback that gets called when the form is submitted
|
11
11
|
* after all validations have been run.
|
12
12
|
*/
|
13
|
-
onSubmit?: (data: DataType, event: React.FormEvent<HTMLFormElement>) => Promise<void>;
|
13
|
+
onSubmit?: (data: DataType, event: React.FormEvent<HTMLFormElement>) => void | Promise<void>;
|
14
14
|
/**
|
15
15
|
* Allows you to provide a `fetcher` from remix's `useFetcher` hook.
|
16
16
|
* The form will use the fetcher for loading states, action data, etc
|
@@ -47,4 +47,4 @@ export declare type FormProps<DataType> = {
|
|
47
47
|
/**
|
48
48
|
* The primary form component of `remix-validated-form`.
|
49
49
|
*/
|
50
|
-
export declare function ValidatedForm<DataType>({ validator, onSubmit, children, fetcher, action, defaultValues:
|
50
|
+
export declare function ValidatedForm<DataType>({ validator, onSubmit, children, fetcher, action, defaultValues: unMemoizedDefaults, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, method, replace, id, ...rest }: FormProps<DataType>): JSX.Element;
|
package/build/ValidatedForm.js
CHANGED
@@ -100,13 +100,35 @@ const FormResetter = ({ resetAfterSubmit, formRef, }) => {
|
|
100
100
|
});
|
101
101
|
return null;
|
102
102
|
};
|
103
|
+
function formEventProxy(event) {
|
104
|
+
let defaultPrevented = false;
|
105
|
+
return new Proxy(event, {
|
106
|
+
get: (target, prop) => {
|
107
|
+
if (prop === "preventDefault") {
|
108
|
+
return () => {
|
109
|
+
defaultPrevented = true;
|
110
|
+
};
|
111
|
+
}
|
112
|
+
if (prop === "defaultPrevented") {
|
113
|
+
return defaultPrevented;
|
114
|
+
}
|
115
|
+
return target[prop];
|
116
|
+
},
|
117
|
+
});
|
118
|
+
}
|
119
|
+
const useFormAtom = (formId) => {
|
120
|
+
const formAtom = (0, state_1.formRegistry)(formId);
|
121
|
+
(0, react_2.useEffect)(() => () => state_1.formRegistry.remove(formId), [formId]);
|
122
|
+
return formAtom;
|
123
|
+
};
|
103
124
|
/**
|
104
125
|
* The primary form component of `remix-validated-form`.
|
105
126
|
*/
|
106
|
-
function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues:
|
127
|
+
function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues: unMemoizedDefaults, formRef: formRefProp, onReset, subaction, resetAfterSubmit = false, disableFocusOnError, method, replace, id, ...rest }) {
|
107
128
|
var _a;
|
108
129
|
const formId = useFormId(id);
|
109
|
-
const formAtom = (
|
130
|
+
const formAtom = useFormAtom(formId);
|
131
|
+
const providedDefaultValues = (0, util_1.useDeepEqualsMemo)(unMemoizedDefaults);
|
110
132
|
const contextValue = (0, react_2.useMemo)(() => ({
|
111
133
|
formId,
|
112
134
|
action,
|
@@ -195,28 +217,36 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
|
|
195
217
|
window.removeEventListener("click", handleClick);
|
196
218
|
};
|
197
219
|
}, []);
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
focusFirstInvalidInput(result.error.fieldErrors, customFocusHandlers(), formRef.current);
|
207
|
-
}
|
220
|
+
const handleSubmit = async (e) => {
|
221
|
+
startSubmit({ formAtom });
|
222
|
+
const result = await validator.validate(getDataFromForm(e.currentTarget));
|
223
|
+
if (result.error) {
|
224
|
+
endSubmit({ formAtom });
|
225
|
+
setFieldErrors({ fieldErrors: result.error.fieldErrors, formAtom });
|
226
|
+
if (!disableFocusOnError) {
|
227
|
+
focusFirstInvalidInput(result.error.fieldErrors, customFocusHandlers(), formRef.current);
|
208
228
|
}
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
replace,
|
217
|
-
});
|
218
|
-
clickedButtonRef.current = null;
|
229
|
+
}
|
230
|
+
else {
|
231
|
+
const eventProxy = formEventProxy(e);
|
232
|
+
await (onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(result.data, eventProxy));
|
233
|
+
if (eventProxy.defaultPrevented) {
|
234
|
+
endSubmit({ formAtom });
|
235
|
+
return;
|
219
236
|
}
|
237
|
+
if (fetcher)
|
238
|
+
fetcher.submit(clickedButtonRef.current || e.currentTarget);
|
239
|
+
else
|
240
|
+
submit(clickedButtonRef.current || e.currentTarget, {
|
241
|
+
method,
|
242
|
+
replace,
|
243
|
+
});
|
244
|
+
clickedButtonRef.current = null;
|
245
|
+
}
|
246
|
+
};
|
247
|
+
return ((0, jsx_runtime_1.jsx)(Form, { ref: (0, util_1.mergeRefs)([formRef, formRefProp]), ...rest, id: id, action: action, method: method, replace: replace, onSubmit: (e) => {
|
248
|
+
e.preventDefault();
|
249
|
+
handleSubmit(e);
|
220
250
|
}, onReset: (event) => {
|
221
251
|
onReset === null || onReset === void 0 ? void 0 : onReset(event);
|
222
252
|
if (event.defaultPrevented)
|
package/build/hooks.js
CHANGED
@@ -12,14 +12,20 @@ const state_1 = require("./internal/state");
|
|
12
12
|
*
|
13
13
|
* @param formId
|
14
14
|
*/
|
15
|
-
const useIsSubmitting = (formId) =>
|
15
|
+
const useIsSubmitting = (formId) => {
|
16
|
+
const formContext = (0, hooks_1.useInternalFormContext)(formId, "useIsSubmitting");
|
17
|
+
return (0, hooks_1.useContextSelectAtom)(formContext.formId, state_1.isSubmittingAtom);
|
18
|
+
};
|
16
19
|
exports.useIsSubmitting = useIsSubmitting;
|
17
20
|
/**
|
18
21
|
* Returns whether or not the current form is valid.
|
19
22
|
*
|
20
23
|
* @param formId the id of the form. Only necessary if being used outside a ValidatedForm.
|
21
24
|
*/
|
22
|
-
const useIsValid = (formId) =>
|
25
|
+
const useIsValid = (formId) => {
|
26
|
+
const formContext = (0, hooks_1.useInternalFormContext)(formId, "useIsValid");
|
27
|
+
return (0, hooks_1.useContextSelectAtom)(formContext.formId, state_1.isValidAtom);
|
28
|
+
};
|
23
29
|
exports.useIsValid = useIsValid;
|
24
30
|
/**
|
25
31
|
* Provides the data and helpers necessary to set up a field.
|
@@ -34,11 +34,11 @@ class MultiValueMap {
|
|
34
34
|
exports.MultiValueMap = MultiValueMap;
|
35
35
|
const useMultiValueMap = () => {
|
36
36
|
const ref = (0, react_1.useRef)(null);
|
37
|
-
return () => {
|
37
|
+
return (0, react_1.useCallback)(() => {
|
38
38
|
if (ref.current)
|
39
39
|
return ref.current;
|
40
40
|
ref.current = new MultiValueMap();
|
41
41
|
return ref.current;
|
42
|
-
};
|
42
|
+
}, []);
|
43
43
|
};
|
44
44
|
exports.useMultiValueMap = useMultiValueMap;
|
@@ -7,7 +7,6 @@ declare type FormSelectorAtomCreator<T> = (formState: FormAtom) => Atom<T>;
|
|
7
7
|
declare const USE_HYDRATED_STATE: unique symbol;
|
8
8
|
export declare const useInternalFormContext: (formId?: string | symbol | undefined, hookName?: string | undefined) => InternalFormContextValue;
|
9
9
|
export declare const useContextSelectAtom: <T>(formId: string | symbol, selectorAtomCreator: FormSelectorAtomCreator<T>) => T extends Promise<infer V> ? V : T;
|
10
|
-
export declare const useUnknownFormContextSelectAtom: <T>(formId: string | symbol | undefined, selectorAtomCreator: FormSelectorAtomCreator<T>, hookName: string) => T extends Promise<infer V> ? V : T;
|
11
10
|
export declare const useHydratableSelector: <T, U>({ formId }: InternalFormContextValue, atomCreator: FormSelectorAtomCreator<T>, dataToUse: U | typeof USE_HYDRATED_STATE, selector?: (data: U) => T) => T | (T extends Promise<infer V> ? V : T);
|
12
11
|
export declare function useErrorResponseForForm({ fetcher, subaction, formId, }: InternalFormContextValue): ValidationErrorResponseData | null;
|
13
12
|
export declare const useFieldErrorsForForm: (context: InternalFormContextValue) => import("..").FieldErrors | typeof USE_HYDRATED_STATE | undefined;
|
package/build/internal/hooks.js
CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.useSetTouched = exports.useClearError = exports.useFormUpdateAtom = exports.useFieldDefaultValue = exports.useFieldError = exports.useFieldTouched = exports.useHasActiveFormSubmit = exports.useDefaultValuesForForm = exports.useDefaultValuesFromLoader = exports.useFieldErrorsForForm = exports.useErrorResponseForForm = exports.useHydratableSelector = exports.
|
6
|
+
exports.useSetTouched = exports.useClearError = exports.useFormUpdateAtom = exports.useFieldDefaultValue = exports.useFieldError = exports.useFieldTouched = exports.useHasActiveFormSubmit = exports.useDefaultValuesForForm = exports.useDefaultValuesFromLoader = exports.useFieldErrorsForForm = exports.useErrorResponseForForm = exports.useHydratableSelector = exports.useContextSelectAtom = exports.useInternalFormContext = void 0;
|
7
7
|
const react_1 = require("@remix-run/react");
|
8
8
|
const utils_1 = require("jotai/utils");
|
9
9
|
const get_1 = __importDefault(require("lodash/get"));
|
@@ -28,11 +28,6 @@ const useContextSelectAtom = (formId, selectorAtomCreator) => {
|
|
28
28
|
return (0, utils_1.useAtomValue)(selectorAtom, state_1.ATOM_SCOPE);
|
29
29
|
};
|
30
30
|
exports.useContextSelectAtom = useContextSelectAtom;
|
31
|
-
const useUnknownFormContextSelectAtom = (formId, selectorAtomCreator, hookName) => {
|
32
|
-
const formContext = (0, exports.useInternalFormContext)(formId, hookName);
|
33
|
-
return (0, exports.useContextSelectAtom)(formContext.formId, selectorAtomCreator);
|
34
|
-
};
|
35
|
-
exports.useUnknownFormContextSelectAtom = useUnknownFormContextSelectAtom;
|
36
31
|
const useHydratableSelector = ({ formId }, atomCreator, dataToUse, selector = identity_1.default) => {
|
37
32
|
const dataFromState = (0, exports.useContextSelectAtom)(formId, atomCreator);
|
38
33
|
return dataToUse === USE_HYDRATED_STATE ? dataFromState : selector(dataToUse);
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const getCheckboxChecked: (checkboxValue: string | undefined, newValue: unknown) => boolean | undefined;
|
@@ -0,0 +1,13 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.getCheckboxChecked = void 0;
|
4
|
+
const getCheckboxChecked = (checkboxValue = "on", newValue) => {
|
5
|
+
if (Array.isArray(newValue))
|
6
|
+
return newValue.includes(checkboxValue);
|
7
|
+
if (typeof newValue === "boolean")
|
8
|
+
return newValue;
|
9
|
+
if (typeof newValue === "string")
|
10
|
+
return newValue === checkboxValue;
|
11
|
+
return undefined;
|
12
|
+
};
|
13
|
+
exports.getCheckboxChecked = getCheckboxChecked;
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const getRadioChecked: (radioValue: string | undefined, newValue: unknown) => boolean | undefined;
|
@@ -0,0 +1,9 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.getRadioChecked = void 0;
|
4
|
+
const getRadioChecked = (radioValue = "on", newValue) => {
|
5
|
+
if (typeof newValue === "string")
|
6
|
+
return newValue === radioValue;
|
7
|
+
return undefined;
|
8
|
+
};
|
9
|
+
exports.getRadioChecked = getRadioChecked;
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const setFieldValue: (formElement: HTMLFormElement, name: string, value: unknown) => void;
|
@@ -0,0 +1,47 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.setFieldValue = void 0;
|
7
|
+
const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
|
8
|
+
const getCheckboxChecked_1 = require("./getCheckboxChecked");
|
9
|
+
const getRadioChecked_1 = require("./getRadioChecked");
|
10
|
+
const setElementValue = (element, value, name) => {
|
11
|
+
if (element instanceof HTMLSelectElement && element.multiple) {
|
12
|
+
(0, tiny_invariant_1.default)(Array.isArray(value), "Must specify an array to set the value for a multi-select");
|
13
|
+
for (const option of element.options) {
|
14
|
+
option.selected = value.includes(option.value);
|
15
|
+
}
|
16
|
+
return;
|
17
|
+
}
|
18
|
+
if (element instanceof HTMLInputElement && element.type === "checkbox") {
|
19
|
+
const newChecked = (0, getCheckboxChecked_1.getCheckboxChecked)(element.value, value);
|
20
|
+
(0, tiny_invariant_1.default)(newChecked, `Unable to determine if checkbox should be checked. Provided value was ${value} for checkbox ${name}.`);
|
21
|
+
element.checked = newChecked;
|
22
|
+
return;
|
23
|
+
}
|
24
|
+
if (element instanceof HTMLInputElement && element.type === "radio") {
|
25
|
+
const newChecked = (0, getRadioChecked_1.getRadioChecked)(element.value, value);
|
26
|
+
(0, tiny_invariant_1.default)(newChecked, `Unable to determine if radio should be checked. Provided value was ${value} for radio ${name}.`);
|
27
|
+
element.checked = newChecked;
|
28
|
+
return;
|
29
|
+
}
|
30
|
+
(0, tiny_invariant_1.default)(typeof value === "string", `Must specify a string to set the value of ${element.constructor.name}`);
|
31
|
+
const input = element;
|
32
|
+
input.value = value;
|
33
|
+
};
|
34
|
+
const setFieldValue = (formElement, name, value) => {
|
35
|
+
const controlElement = formElement.elements.namedItem(name);
|
36
|
+
if (!controlElement)
|
37
|
+
return;
|
38
|
+
if (controlElement instanceof RadioNodeList) {
|
39
|
+
for (const element of controlElement) {
|
40
|
+
setElementValue(element, value, name);
|
41
|
+
}
|
42
|
+
}
|
43
|
+
else {
|
44
|
+
setElementValue(controlElement, value, name);
|
45
|
+
}
|
46
|
+
};
|
47
|
+
exports.setFieldValue = setFieldValue;
|
package/build/internal/util.d.ts
CHANGED
@@ -2,3 +2,4 @@ import type React from "react";
|
|
2
2
|
export declare const omit: (obj: any, ...keys: string[]) => any;
|
3
3
|
export declare const mergeRefs: <T = any>(refs: (React.MutableRefObject<T> | React.LegacyRef<T> | undefined)[]) => (instance: T | null) => void;
|
4
4
|
export declare const useIsomorphicLayoutEffect: typeof React.useEffect;
|
5
|
+
export declare const useDeepEqualsMemo: <T>(item: T) => T;
|
package/build/internal/util.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.useIsomorphicLayoutEffect = exports.mergeRefs = exports.omit = void 0;
|
3
|
+
exports.useDeepEqualsMemo = exports.useIsomorphicLayoutEffect = exports.mergeRefs = exports.omit = void 0;
|
4
|
+
const lodash_1 = require("lodash");
|
4
5
|
const react_1 = require("react");
|
5
6
|
const omit = (obj, ...keys) => {
|
6
7
|
const result = { ...obj };
|
@@ -24,3 +25,14 @@ const mergeRefs = (refs) => {
|
|
24
25
|
};
|
25
26
|
exports.mergeRefs = mergeRefs;
|
26
27
|
exports.useIsomorphicLayoutEffect = typeof window !== "undefined" ? react_1.useLayoutEffect : react_1.useEffect;
|
28
|
+
const useDeepEqualsMemo = (item) => {
|
29
|
+
const ref = (0, react_1.useRef)(item);
|
30
|
+
const areEqual = ref.current === item || (0, lodash_1.isEqual)(ref.current, item);
|
31
|
+
(0, react_1.useEffect)(() => {
|
32
|
+
if (!areEqual) {
|
33
|
+
ref.current = item;
|
34
|
+
}
|
35
|
+
});
|
36
|
+
return areEqual ? ref.current : item;
|
37
|
+
};
|
38
|
+
exports.useDeepEqualsMemo = useDeepEqualsMemo;
|
package/package.json
CHANGED
package/src/ValidatedForm.tsx
CHANGED
@@ -2,6 +2,7 @@ import { Form as RemixForm, useFetcher, useSubmit } from "@remix-run/react";
|
|
2
2
|
import uniq from "lodash/uniq";
|
3
3
|
import React, {
|
4
4
|
ComponentProps,
|
5
|
+
FormEvent,
|
5
6
|
RefObject,
|
6
7
|
useCallback,
|
7
8
|
useEffect,
|
@@ -37,6 +38,7 @@ import {
|
|
37
38
|
import { useSubmitComplete } from "./internal/submissionCallbacks";
|
38
39
|
import {
|
39
40
|
mergeRefs,
|
41
|
+
useDeepEqualsMemo,
|
40
42
|
useIsomorphicLayoutEffect as useLayoutEffect,
|
41
43
|
} from "./internal/util";
|
42
44
|
import { FieldErrors, Validator } from "./validation/types";
|
@@ -53,7 +55,7 @@ export type FormProps<DataType> = {
|
|
53
55
|
onSubmit?: (
|
54
56
|
data: DataType,
|
55
57
|
event: React.FormEvent<HTMLFormElement>
|
56
|
-
) => Promise<void>;
|
58
|
+
) => void | Promise<void>;
|
57
59
|
/**
|
58
60
|
* Allows you to provide a `fetcher` from remix's `useFetcher` hook.
|
59
61
|
* The form will use the fetcher for loading states, action data, etc
|
@@ -171,6 +173,31 @@ const FormResetter = ({
|
|
171
173
|
return null;
|
172
174
|
};
|
173
175
|
|
176
|
+
function formEventProxy<T extends object>(event: T): T {
|
177
|
+
let defaultPrevented = false;
|
178
|
+
return new Proxy(event, {
|
179
|
+
get: (target, prop) => {
|
180
|
+
if (prop === "preventDefault") {
|
181
|
+
return () => {
|
182
|
+
defaultPrevented = true;
|
183
|
+
};
|
184
|
+
}
|
185
|
+
|
186
|
+
if (prop === "defaultPrevented") {
|
187
|
+
return defaultPrevented;
|
188
|
+
}
|
189
|
+
|
190
|
+
return target[prop as keyof T];
|
191
|
+
},
|
192
|
+
}) as T;
|
193
|
+
}
|
194
|
+
|
195
|
+
const useFormAtom = (formId: string | symbol) => {
|
196
|
+
const formAtom = formRegistry(formId);
|
197
|
+
useEffect(() => () => formRegistry.remove(formId), [formId]);
|
198
|
+
return formAtom;
|
199
|
+
};
|
200
|
+
|
174
201
|
/**
|
175
202
|
* The primary form component of `remix-validated-form`.
|
176
203
|
*/
|
@@ -180,7 +207,7 @@ export function ValidatedForm<DataType>({
|
|
180
207
|
children,
|
181
208
|
fetcher,
|
182
209
|
action,
|
183
|
-
defaultValues:
|
210
|
+
defaultValues: unMemoizedDefaults,
|
184
211
|
formRef: formRefProp,
|
185
212
|
onReset,
|
186
213
|
subaction,
|
@@ -192,7 +219,8 @@ export function ValidatedForm<DataType>({
|
|
192
219
|
...rest
|
193
220
|
}: FormProps<DataType>) {
|
194
221
|
const formId = useFormId(id);
|
195
|
-
const formAtom =
|
222
|
+
const formAtom = useFormAtom(formId);
|
223
|
+
const providedDefaultValues = useDeepEqualsMemo(unMemoizedDefaults);
|
196
224
|
const contextValue = useMemo<InternalFormContextValue>(
|
197
225
|
() => ({
|
198
226
|
formId,
|
@@ -305,6 +333,37 @@ export function ValidatedForm<DataType>({
|
|
305
333
|
};
|
306
334
|
}, []);
|
307
335
|
|
336
|
+
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
337
|
+
startSubmit({ formAtom });
|
338
|
+
const result = await validator.validate(getDataFromForm(e.currentTarget));
|
339
|
+
if (result.error) {
|
340
|
+
endSubmit({ formAtom });
|
341
|
+
setFieldErrors({ fieldErrors: result.error.fieldErrors, formAtom });
|
342
|
+
if (!disableFocusOnError) {
|
343
|
+
focusFirstInvalidInput(
|
344
|
+
result.error.fieldErrors,
|
345
|
+
customFocusHandlers(),
|
346
|
+
formRef.current!
|
347
|
+
);
|
348
|
+
}
|
349
|
+
} else {
|
350
|
+
const eventProxy = formEventProxy(e);
|
351
|
+
await onSubmit?.(result.data, eventProxy);
|
352
|
+
if (eventProxy.defaultPrevented) {
|
353
|
+
endSubmit({ formAtom });
|
354
|
+
return;
|
355
|
+
}
|
356
|
+
|
357
|
+
if (fetcher) fetcher.submit(clickedButtonRef.current || e.currentTarget);
|
358
|
+
else
|
359
|
+
submit(clickedButtonRef.current || e.currentTarget, {
|
360
|
+
method,
|
361
|
+
replace,
|
362
|
+
});
|
363
|
+
clickedButtonRef.current = null;
|
364
|
+
}
|
365
|
+
};
|
366
|
+
|
308
367
|
return (
|
309
368
|
<Form
|
310
369
|
ref={mergeRefs([formRef, formRefProp])}
|
@@ -313,33 +372,9 @@ export function ValidatedForm<DataType>({
|
|
313
372
|
action={action}
|
314
373
|
method={method}
|
315
374
|
replace={replace}
|
316
|
-
onSubmit={
|
375
|
+
onSubmit={(e) => {
|
317
376
|
e.preventDefault();
|
318
|
-
|
319
|
-
const result = await validator.validate(
|
320
|
-
getDataFromForm(e.currentTarget)
|
321
|
-
);
|
322
|
-
if (result.error) {
|
323
|
-
endSubmit({ formAtom });
|
324
|
-
setFieldErrors({ fieldErrors: result.error.fieldErrors, formAtom });
|
325
|
-
if (!disableFocusOnError) {
|
326
|
-
focusFirstInvalidInput(
|
327
|
-
result.error.fieldErrors,
|
328
|
-
customFocusHandlers(),
|
329
|
-
formRef.current!
|
330
|
-
);
|
331
|
-
}
|
332
|
-
} else {
|
333
|
-
onSubmit?.(result.data, e);
|
334
|
-
if (fetcher)
|
335
|
-
fetcher.submit(clickedButtonRef.current || e.currentTarget);
|
336
|
-
else
|
337
|
-
submit(clickedButtonRef.current || e.currentTarget, {
|
338
|
-
method,
|
339
|
-
replace,
|
340
|
-
});
|
341
|
-
clickedButtonRef.current = null;
|
342
|
-
}
|
377
|
+
handleSubmit(e);
|
343
378
|
}}
|
344
379
|
onReset={(event) => {
|
345
380
|
onReset?.(event);
|
package/src/hooks.ts
CHANGED
@@ -5,7 +5,6 @@ import {
|
|
5
5
|
ValidationBehaviorOptions,
|
6
6
|
} from "./internal/getInputProps";
|
7
7
|
import {
|
8
|
-
useUnknownFormContextSelectAtom,
|
9
8
|
useInternalFormContext,
|
10
9
|
useFieldTouched,
|
11
10
|
useFieldError,
|
@@ -29,16 +28,20 @@ import {
|
|
29
28
|
*
|
30
29
|
* @param formId
|
31
30
|
*/
|
32
|
-
export const useIsSubmitting = (formId?: string) =>
|
33
|
-
|
31
|
+
export const useIsSubmitting = (formId?: string) => {
|
32
|
+
const formContext = useInternalFormContext(formId, "useIsSubmitting");
|
33
|
+
return useContextSelectAtom(formContext.formId, isSubmittingAtom);
|
34
|
+
};
|
34
35
|
|
35
36
|
/**
|
36
37
|
* Returns whether or not the current form is valid.
|
37
38
|
*
|
38
39
|
* @param formId the id of the form. Only necessary if being used outside a ValidatedForm.
|
39
40
|
*/
|
40
|
-
export const useIsValid = (formId?: string) =>
|
41
|
-
|
41
|
+
export const useIsValid = (formId?: string) => {
|
42
|
+
const formContext = useInternalFormContext(formId, "useIsValid");
|
43
|
+
return useContextSelectAtom(formContext.formId, isValidAtom);
|
44
|
+
};
|
42
45
|
|
43
46
|
export type FieldProps = {
|
44
47
|
/**
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { useRef } from "react";
|
1
|
+
import { useCallback, useRef } from "react";
|
2
2
|
|
3
3
|
export class MultiValueMap<Key, Value> {
|
4
4
|
private dict: Map<Key, Value[]> = new Map();
|
@@ -30,9 +30,9 @@ export class MultiValueMap<Key, Value> {
|
|
30
30
|
|
31
31
|
export const useMultiValueMap = <Key, Value>() => {
|
32
32
|
const ref = useRef<MultiValueMap<Key, Value> | null>(null);
|
33
|
-
return () => {
|
33
|
+
return useCallback(() => {
|
34
34
|
if (ref.current) return ref.current;
|
35
35
|
ref.current = new MultiValueMap();
|
36
36
|
return ref.current;
|
37
|
-
};
|
37
|
+
}, []);
|
38
38
|
};
|
package/src/internal/hooks.ts
CHANGED
@@ -48,15 +48,6 @@ export const useContextSelectAtom = <T>(
|
|
48
48
|
return useAtomValue(selectorAtom, ATOM_SCOPE);
|
49
49
|
};
|
50
50
|
|
51
|
-
export const useUnknownFormContextSelectAtom = <T>(
|
52
|
-
formId: string | symbol | undefined,
|
53
|
-
selectorAtomCreator: FormSelectorAtomCreator<T>,
|
54
|
-
hookName: string
|
55
|
-
) => {
|
56
|
-
const formContext = useInternalFormContext(formId, hookName);
|
57
|
-
return useContextSelectAtom(formContext.formId, selectorAtomCreator);
|
58
|
-
};
|
59
|
-
|
60
51
|
export const useHydratableSelector = <T, U>(
|
61
52
|
{ formId }: InternalFormContextValue,
|
62
53
|
atomCreator: FormSelectorAtomCreator<T>,
|
package/src/internal/util.ts
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
+
import { isEqual } from "lodash";
|
1
2
|
import type React from "react";
|
2
|
-
import { useEffect, useLayoutEffect } from "react";
|
3
|
+
import { useEffect, useLayoutEffect, useRef } from "react";
|
3
4
|
|
4
5
|
export const omit = (obj: any, ...keys: string[]) => {
|
5
6
|
const result = { ...obj };
|
@@ -25,3 +26,14 @@ export const mergeRefs = <T = any>(
|
|
25
26
|
|
26
27
|
export const useIsomorphicLayoutEffect =
|
27
28
|
typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
29
|
+
|
30
|
+
export const useDeepEqualsMemo = <T>(item: T): T => {
|
31
|
+
const ref = useRef<T>(item);
|
32
|
+
const areEqual = ref.current === item || isEqual(ref.current, item);
|
33
|
+
useEffect(() => {
|
34
|
+
if (!areEqual) {
|
35
|
+
ref.current = item;
|
36
|
+
}
|
37
|
+
});
|
38
|
+
return areEqual ? ref.current : item;
|
39
|
+
};
|