remix-validated-form 4.1.0 → 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.
@@ -1,9 +1,9 @@
1
1
  $ npm run build:browser && npm run build:main
2
2
 
3
- > remix-validated-form@4.0.2 build:browser
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.2 build:main
7
+ > remix-validated-form@4.1.0 build:main
8
8
  > tsc --module CommonJS --outDir ./build
9
9
 
@@ -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: providedDefaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, method, replace, id, ...rest }: FormProps<DataType>): JSX.Element;
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;
@@ -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;
@@ -99,10 +99,11 @@ const useFormAtom = (formId) => {
99
99
  /**
100
100
  * The primary form component of `remix-validated-form`.
101
101
  */
102
- export function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues: providedDefaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit = false, disableFocusOnError, method, replace, id, ...rest }) {
102
+ export function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues: unMemoizedDefaults, formRef: formRefProp, onReset, subaction, resetAfterSubmit = false, disableFocusOnError, method, replace, id, ...rest }) {
103
103
  var _a;
104
104
  const formId = useFormId(id);
105
105
  const formAtom = useFormAtom(formId);
106
+ const providedDefaultValues = useDeepEqualsMemo(unMemoizedDefaults);
106
107
  const contextValue = useMemo(() => ({
107
108
  formId,
108
109
  action,
@@ -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
  };
@@ -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,5 @@
1
+ export const getRadioChecked = (radioValue = "on", newValue) => {
2
+ if (typeof newValue === "string")
3
+ return newValue === radioValue;
4
+ return undefined;
5
+ };
@@ -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;
@@ -1,4 +1,5 @@
1
- import { useEffect, useLayoutEffect } from "react";
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
+ };
@@ -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: providedDefaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, method, replace, id, ...rest }: FormProps<DataType>): JSX.Element;
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;
@@ -124,10 +124,11 @@ const useFormAtom = (formId) => {
124
124
  /**
125
125
  * The primary form component of `remix-validated-form`.
126
126
  */
127
- function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues: providedDefaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit = false, disableFocusOnError, method, replace, id, ...rest }) {
127
+ function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues: unMemoizedDefaults, formRef: formRefProp, onReset, subaction, resetAfterSubmit = false, disableFocusOnError, method, replace, id, ...rest }) {
128
128
  var _a;
129
129
  const formId = useFormId(id);
130
130
  const formAtom = useFormAtom(formId);
131
+ const providedDefaultValues = (0, util_1.useDeepEqualsMemo)(unMemoizedDefaults);
131
132
  const contextValue = (0, react_2.useMemo)(() => ({
132
133
  formId,
133
134
  action,
@@ -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;
@@ -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;
@@ -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;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remix-validated-form",
3
- "version": "4.1.0",
3
+ "version": "4.1.1",
4
4
  "description": "Form component and utils for easy form validation in remix",
5
5
  "browser": "./browser/index.js",
6
6
  "main": "./build/index.js",
@@ -38,6 +38,7 @@ import {
38
38
  import { useSubmitComplete } from "./internal/submissionCallbacks";
39
39
  import {
40
40
  mergeRefs,
41
+ useDeepEqualsMemo,
41
42
  useIsomorphicLayoutEffect as useLayoutEffect,
42
43
  } from "./internal/util";
43
44
  import { FieldErrors, Validator } from "./validation/types";
@@ -206,7 +207,7 @@ export function ValidatedForm<DataType>({
206
207
  children,
207
208
  fetcher,
208
209
  action,
209
- defaultValues: providedDefaultValues,
210
+ defaultValues: unMemoizedDefaults,
210
211
  formRef: formRefProp,
211
212
  onReset,
212
213
  subaction,
@@ -219,6 +220,7 @@ export function ValidatedForm<DataType>({
219
220
  }: FormProps<DataType>) {
220
221
  const formId = useFormId(id);
221
222
  const formAtom = useFormAtom(formId);
223
+ const providedDefaultValues = useDeepEqualsMemo(unMemoizedDefaults);
222
224
  const contextValue = useMemo<InternalFormContextValue>(
223
225
  () => ({
224
226
  formId,
@@ -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
  };
@@ -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
+ };