remix-validated-form 3.2.1 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/README.md +4 -2
- package/browser/ValidatedForm.js +16 -6
- package/browser/internal/MultiValueMap.d.ts +9 -0
- package/browser/internal/MultiValueMap.js +39 -1
- package/browser/internal/SingleTypeMultiValueMap.d.ts +1 -0
- package/browser/internal/SingleTypeMultiValueMap.js +1 -0
- package/browser/internal/flatten.d.ts +0 -3
- package/browser/internal/flatten.js +6 -34
- package/browser/internal/getInputProps.d.ts +10 -2
- package/browser/internal/getInputProps.js +23 -3
- package/browser/internal/test.d.ts +0 -1
- package/browser/internal/test.js +15 -10
- package/browser/internal/useMultiValueMap.d.ts +1 -0
- package/browser/internal/useMultiValueMap.js +11 -0
- package/build/ValidatedForm.js +17 -7
- package/build/internal/MultiValueMap.d.ts +9 -0
- package/build/internal/MultiValueMap.js +44 -0
- package/build/internal/flatten.d.ts +0 -3
- package/build/internal/flatten.js +7 -36
- package/build/internal/getInputProps.d.ts +10 -2
- package/build/internal/getInputProps.js +23 -3
- package/build/internal/test.d.ts +0 -0
- package/build/internal/test.js +0 -0
- package/package.json +1 -1
- package/src/ValidatedForm.tsx +15 -9
- package/src/internal/{SingleTypeMultiValueMap.ts → MultiValueMap.ts} +2 -1
- package/src/internal/flatten.ts +7 -44
- package/src/internal/getInputProps.ts +34 -6
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@3.
|
3
|
+
> remix-validated-form@3.3.1 build:browser
|
4
4
|
> tsc --module ESNext --outDir ./browser
|
5
5
|
|
6
6
|
|
7
|
-
> remix-validated-form@3.
|
7
|
+
> remix-validated-form@3.3.1 build:main
|
8
8
|
> tsc --module CommonJS --outDir ./build
|
9
9
|
|
package/README.md
CHANGED
@@ -50,6 +50,8 @@ see the [Validation library support](#validation-library-support) section below.
|
|
50
50
|
npm install @remix-validated-form/with-zod
|
51
51
|
```
|
52
52
|
|
53
|
+
If you're using zod, you might also find `zod-form-data` helpful.
|
54
|
+
|
53
55
|
## Create an input component
|
54
56
|
|
55
57
|
In order to display field errors or do field-by-field validation,
|
@@ -63,7 +65,7 @@ type MyInputProps = {
|
|
63
65
|
label: string;
|
64
66
|
};
|
65
67
|
|
66
|
-
export const MyInput = ({ name, label }:
|
68
|
+
export const MyInput = ({ name, label }: MyInputProps) => {
|
67
69
|
const { error, getInputProps } = useField(name);
|
68
70
|
return (
|
69
71
|
<div>
|
@@ -257,4 +259,4 @@ See the [Remix](https://remix.run/docs/en/v1/api/remix#sessionflashkey-value) do
|
|
257
259
|
|
258
260
|
## Why is my cancel button triggering form submission?
|
259
261
|
Problem: the cancel button has an onClick handler to navigate away from the form route but instead it is submitting the form.
|
260
|
-
A button defaults to `type="submit"` in a form which will submit the form by default. If you want to prevent this you can add `type="reset"` or `type="button"` to the cancel button.
|
262
|
+
A button defaults to `type="submit"` in a form which will submit the form by default. If you want to prevent this you can add `type="reset"` or `type="button"` to the cancel button.
|
package/browser/ValidatedForm.js
CHANGED
@@ -3,7 +3,7 @@ import { Form as RemixForm, useActionData, useFormAction, useTransition, } from
|
|
3
3
|
import { useEffect, useMemo, useRef, useState, } from "react";
|
4
4
|
import invariant from "tiny-invariant";
|
5
5
|
import { FormContext } from "./internal/formContext";
|
6
|
-
import { useMultiValueMap
|
6
|
+
import { useMultiValueMap } from "./internal/MultiValueMap";
|
7
7
|
import { useSubmitComplete } from "./internal/submissionCallbacks";
|
8
8
|
import { omit, mergeRefs } from "./internal/util";
|
9
9
|
function useFieldErrorsFromBackend(fetcher, subaction) {
|
@@ -123,14 +123,24 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
123
123
|
validateField: (fieldName) => {
|
124
124
|
invariant(formRef.current, "Cannot find reference to form");
|
125
125
|
const { error } = validator.validateField(getDataFromForm(formRef.current), fieldName);
|
126
|
+
// By checking and returning `prev` here, we can avoid a re-render
|
127
|
+
// if the validation state is the same.
|
126
128
|
if (error) {
|
127
|
-
setFieldErrors((prev) =>
|
128
|
-
|
129
|
-
|
130
|
-
|
129
|
+
setFieldErrors((prev) => {
|
130
|
+
if (prev[fieldName] === error)
|
131
|
+
return prev;
|
132
|
+
return {
|
133
|
+
...prev,
|
134
|
+
[fieldName]: error,
|
135
|
+
};
|
136
|
+
});
|
131
137
|
}
|
132
138
|
else {
|
133
|
-
setFieldErrors((prev) =>
|
139
|
+
setFieldErrors((prev) => {
|
140
|
+
if (!(fieldName in prev))
|
141
|
+
return prev;
|
142
|
+
return omit(prev, fieldName);
|
143
|
+
});
|
134
144
|
}
|
135
145
|
},
|
136
146
|
registerReceiveFocus: (fieldName, handler) => {
|
@@ -0,0 +1,9 @@
|
|
1
|
+
export declare class MultiValueMap<Key, Value> {
|
2
|
+
private dict;
|
3
|
+
add: (key: Key, value: Value) => void;
|
4
|
+
remove: (key: Key, value: Value) => void;
|
5
|
+
getAll: (key: Key) => Value[];
|
6
|
+
entries: () => IterableIterator<[Key, Value[]]>;
|
7
|
+
has: (key: Key) => boolean;
|
8
|
+
}
|
9
|
+
export declare const useMultiValueMap: <Key, Value>() => () => MultiValueMap<Key, Value>;
|
@@ -1 +1,39 @@
|
|
1
|
-
|
1
|
+
import { useRef } from "react";
|
2
|
+
export class MultiValueMap {
|
3
|
+
constructor() {
|
4
|
+
this.dict = new Map();
|
5
|
+
this.add = (key, value) => {
|
6
|
+
if (this.dict.has(key)) {
|
7
|
+
this.dict.get(key).push(value);
|
8
|
+
}
|
9
|
+
else {
|
10
|
+
this.dict.set(key, [value]);
|
11
|
+
}
|
12
|
+
};
|
13
|
+
this.remove = (key, value) => {
|
14
|
+
if (!this.dict.has(key))
|
15
|
+
return;
|
16
|
+
const array = this.dict.get(key);
|
17
|
+
const index = array.indexOf(value);
|
18
|
+
if (index !== -1)
|
19
|
+
array.splice(index, 1);
|
20
|
+
if (array.length === 0)
|
21
|
+
this.dict.delete(key);
|
22
|
+
};
|
23
|
+
this.getAll = (key) => {
|
24
|
+
var _a;
|
25
|
+
return (_a = this.dict.get(key)) !== null && _a !== void 0 ? _a : [];
|
26
|
+
};
|
27
|
+
this.entries = () => this.dict.entries();
|
28
|
+
this.has = (key) => this.dict.has(key);
|
29
|
+
}
|
30
|
+
}
|
31
|
+
export const useMultiValueMap = () => {
|
32
|
+
const ref = useRef(null);
|
33
|
+
return () => {
|
34
|
+
if (ref.current)
|
35
|
+
return ref.current;
|
36
|
+
ref.current = new MultiValueMap();
|
37
|
+
return ref.current;
|
38
|
+
};
|
39
|
+
};
|
@@ -3,6 +3,7 @@ export declare class MultiValueMap<Key, Value> {
|
|
3
3
|
add: (key: Key, value: Value) => void;
|
4
4
|
remove: (key: Key, value: Value) => void;
|
5
5
|
getAll: (key: Key) => Value[];
|
6
|
+
entries: () => IterableIterator<[Key, Value[]]>;
|
6
7
|
has: (key: Key) => boolean;
|
7
8
|
}
|
8
9
|
export declare const useMultiValueMap: <Key, Value>() => () => MultiValueMap<Key, Value>;
|
@@ -1,4 +1 @@
|
|
1
|
-
import { GenericObject } from "..";
|
2
1
|
export declare const objectFromPathEntries: (entries: [string, any][]) => {};
|
3
|
-
/** Flatten an object so there are no nested objects or arrays */
|
4
|
-
export declare function flatten(obj: GenericObject, preserveEmpty?: boolean): GenericObject;
|
@@ -1,35 +1,7 @@
|
|
1
|
-
// `flatten` is taken from https://github.com/richie5um/FlattenJS. Decided to implement them here instead of using that package because this is a core functionality of the library and this will add more flexibility in case we need to change the implementation.
|
2
|
-
import assign from "lodash/assign";
|
3
|
-
import isArray from "lodash/isArray";
|
4
|
-
import isObject from "lodash/isObject";
|
5
|
-
import keys from "lodash/keys";
|
6
|
-
import mapKeys from "lodash/mapKeys";
|
7
1
|
import set from "lodash/set";
|
8
|
-
import
|
9
|
-
export const objectFromPathEntries = (entries) =>
|
10
|
-
|
11
|
-
|
12
|
-
return
|
13
|
-
|
14
|
-
let flatMap = mapKeys(flatten(value, preserveEmpty), function (_mvalue, mkey) {
|
15
|
-
if (isArray(value)) {
|
16
|
-
let index = mkey.indexOf(".");
|
17
|
-
if (-1 !== index) {
|
18
|
-
return `${key}[${mkey.slice(0, index)}]${mkey.slice(index)}`;
|
19
|
-
}
|
20
|
-
return `${key}[${mkey}]`;
|
21
|
-
}
|
22
|
-
return `${key}.${mkey}`;
|
23
|
-
});
|
24
|
-
assign(result, flatMap);
|
25
|
-
// Preverve Empty arrays and objects
|
26
|
-
if (preserveEmpty && keys(flatMap).length === 0) {
|
27
|
-
result[key] = value;
|
28
|
-
}
|
29
|
-
}
|
30
|
-
else {
|
31
|
-
result[key] = value;
|
32
|
-
}
|
33
|
-
return result;
|
34
|
-
}, {});
|
35
|
-
}
|
2
|
+
import { MultiValueMap } from "./MultiValueMap";
|
3
|
+
export const objectFromPathEntries = (entries) => {
|
4
|
+
const map = new MultiValueMap();
|
5
|
+
entries.forEach(([key, value]) => map.add(key, value));
|
6
|
+
return [...map.entries()].reduce((acc, [key, value]) => set(acc, key, value.length === 1 ? value[0] : value), {});
|
7
|
+
};
|
@@ -14,8 +14,16 @@ export declare type CreateGetInputPropsOptions = {
|
|
14
14
|
validationBehavior?: Partial<ValidationBehaviorOptions>;
|
15
15
|
name: string;
|
16
16
|
};
|
17
|
-
declare type HandledProps = "name" | "defaultValue";
|
17
|
+
declare type HandledProps = "name" | "defaultValue" | "defaultChecked";
|
18
18
|
declare type Callbacks = "onChange" | "onBlur";
|
19
|
-
|
19
|
+
declare type MinimalInputProps = {
|
20
|
+
onChange?: (...args: any[]) => void;
|
21
|
+
onBlur?: (...args: any[]) => void;
|
22
|
+
defaultValue?: any;
|
23
|
+
defaultChecked?: boolean;
|
24
|
+
name?: string;
|
25
|
+
type?: string;
|
26
|
+
};
|
27
|
+
export declare type GetInputProps = <T extends MinimalInputProps>(props?: Omit<T, HandledProps | Callbacks> & Partial<Pick<T, Callbacks>>) => T;
|
20
28
|
export declare const createGetInputProps: ({ clearError, validate, defaultValue, touched, setTouched, hasBeenSubmitted, validationBehavior, name, }: CreateGetInputPropsOptions) => GetInputProps;
|
21
29
|
export {};
|
@@ -3,18 +3,28 @@ const defaultValidationBehavior = {
|
|
3
3
|
whenTouched: "onChange",
|
4
4
|
whenSubmitted: "onChange",
|
5
5
|
};
|
6
|
+
const getCheckboxDefaultChecked = (value, defaultValue) => {
|
7
|
+
if (Array.isArray(defaultValue))
|
8
|
+
defaultValue.includes(value);
|
9
|
+
if (typeof defaultValue === "boolean")
|
10
|
+
return defaultValue;
|
11
|
+
if (typeof defaultValue === "string")
|
12
|
+
return defaultValue === value;
|
13
|
+
return undefined;
|
14
|
+
};
|
6
15
|
export const createGetInputProps = ({ clearError, validate, defaultValue, touched, setTouched, hasBeenSubmitted, validationBehavior, name, }) => {
|
7
16
|
const validationBehaviors = {
|
8
17
|
...defaultValidationBehavior,
|
9
18
|
...validationBehavior,
|
10
19
|
};
|
11
20
|
return (props = {}) => {
|
21
|
+
var _a, _b;
|
12
22
|
const behavior = hasBeenSubmitted
|
13
23
|
? validationBehaviors.whenSubmitted
|
14
24
|
: touched
|
15
25
|
? validationBehaviors.whenTouched
|
16
26
|
: validationBehaviors.initial;
|
17
|
-
const
|
27
|
+
const inputProps = {
|
18
28
|
...props,
|
19
29
|
onChange: (...args) => {
|
20
30
|
var _a;
|
@@ -31,9 +41,19 @@ export const createGetInputProps = ({ clearError, validate, defaultValue, touche
|
|
31
41
|
setTouched(true);
|
32
42
|
return (_a = props === null || props === void 0 ? void 0 : props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, ...args);
|
33
43
|
},
|
34
|
-
defaultValue,
|
35
44
|
name,
|
36
45
|
};
|
37
|
-
|
46
|
+
if (inputProps.type === "checkbox") {
|
47
|
+
const value = (_a = props.value) !== null && _a !== void 0 ? _a : "on";
|
48
|
+
inputProps.defaultChecked = getCheckboxDefaultChecked(value, defaultValue);
|
49
|
+
}
|
50
|
+
else if (inputProps.type === "radio") {
|
51
|
+
const value = (_b = props.value) !== null && _b !== void 0 ? _b : "on";
|
52
|
+
inputProps.defaultChecked = defaultValue === value;
|
53
|
+
}
|
54
|
+
else {
|
55
|
+
inputProps.defaultValue = defaultValue;
|
56
|
+
}
|
57
|
+
return inputProps;
|
38
58
|
};
|
39
59
|
};
|
@@ -1 +0,0 @@
|
|
1
|
-
export {};
|
package/browser/internal/test.js
CHANGED
@@ -1,10 +1,15 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
}
|
1
|
+
"use strict";
|
2
|
+
z.preprocess((val) => {
|
3
|
+
// Somewhat awkward -- this gets processed per item in the form,
|
4
|
+
// but as a whole array in the backend
|
5
|
+
if (Array.isArray(val)) {
|
6
|
+
return val;
|
7
|
+
}
|
8
|
+
else {
|
9
|
+
return [val];
|
10
|
+
}
|
11
|
+
}, z.array(z.preprocess((val) => {
|
12
|
+
return typeof val !== "string" || val === ""
|
13
|
+
? undefined
|
14
|
+
: Number.parseInt(val);
|
15
|
+
}, z.number())));
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare const useMultiValueMap: <Key, Value>() => () => any;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { MultiValueMap } from "multi-value-map";
|
2
|
+
import { useRef } from "react";
|
3
|
+
export const useMultiValueMap = () => {
|
4
|
+
const ref = useRef(null);
|
5
|
+
return () => {
|
6
|
+
if (ref.current)
|
7
|
+
return ref.current;
|
8
|
+
ref.current = new MultiValueMap();
|
9
|
+
return ref.current;
|
10
|
+
};
|
11
|
+
};
|
package/build/ValidatedForm.js
CHANGED
@@ -9,7 +9,7 @@ const react_1 = require("@remix-run/react");
|
|
9
9
|
const react_2 = require("react");
|
10
10
|
const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
|
11
11
|
const formContext_1 = require("./internal/formContext");
|
12
|
-
const
|
12
|
+
const MultiValueMap_1 = require("./internal/MultiValueMap");
|
13
13
|
const submissionCallbacks_1 = require("./internal/submissionCallbacks");
|
14
14
|
const util_1 = require("./internal/util");
|
15
15
|
function useFieldErrorsFromBackend(fetcher, subaction) {
|
@@ -111,7 +111,7 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
|
|
111
111
|
(_a = formRef.current) === null || _a === void 0 ? void 0 : _a.reset();
|
112
112
|
}
|
113
113
|
});
|
114
|
-
const customFocusHandlers = (0,
|
114
|
+
const customFocusHandlers = (0, MultiValueMap_1.useMultiValueMap)();
|
115
115
|
const contextValue = (0, react_2.useMemo)(() => ({
|
116
116
|
fieldErrors,
|
117
117
|
action,
|
@@ -129,14 +129,24 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
|
|
129
129
|
validateField: (fieldName) => {
|
130
130
|
(0, tiny_invariant_1.default)(formRef.current, "Cannot find reference to form");
|
131
131
|
const { error } = validator.validateField(getDataFromForm(formRef.current), fieldName);
|
132
|
+
// By checking and returning `prev` here, we can avoid a re-render
|
133
|
+
// if the validation state is the same.
|
132
134
|
if (error) {
|
133
|
-
setFieldErrors((prev) =>
|
134
|
-
|
135
|
-
|
136
|
-
|
135
|
+
setFieldErrors((prev) => {
|
136
|
+
if (prev[fieldName] === error)
|
137
|
+
return prev;
|
138
|
+
return {
|
139
|
+
...prev,
|
140
|
+
[fieldName]: error,
|
141
|
+
};
|
142
|
+
});
|
137
143
|
}
|
138
144
|
else {
|
139
|
-
setFieldErrors((prev) =>
|
145
|
+
setFieldErrors((prev) => {
|
146
|
+
if (!(fieldName in prev))
|
147
|
+
return prev;
|
148
|
+
return (0, util_1.omit)(prev, fieldName);
|
149
|
+
});
|
140
150
|
}
|
141
151
|
},
|
142
152
|
registerReceiveFocus: (fieldName, handler) => {
|
@@ -0,0 +1,9 @@
|
|
1
|
+
export declare class MultiValueMap<Key, Value> {
|
2
|
+
private dict;
|
3
|
+
add: (key: Key, value: Value) => void;
|
4
|
+
remove: (key: Key, value: Value) => void;
|
5
|
+
getAll: (key: Key) => Value[];
|
6
|
+
entries: () => IterableIterator<[Key, Value[]]>;
|
7
|
+
has: (key: Key) => boolean;
|
8
|
+
}
|
9
|
+
export declare const useMultiValueMap: <Key, Value>() => () => MultiValueMap<Key, Value>;
|
@@ -0,0 +1,44 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.useMultiValueMap = exports.MultiValueMap = void 0;
|
4
|
+
const react_1 = require("react");
|
5
|
+
class MultiValueMap {
|
6
|
+
constructor() {
|
7
|
+
this.dict = new Map();
|
8
|
+
this.add = (key, value) => {
|
9
|
+
if (this.dict.has(key)) {
|
10
|
+
this.dict.get(key).push(value);
|
11
|
+
}
|
12
|
+
else {
|
13
|
+
this.dict.set(key, [value]);
|
14
|
+
}
|
15
|
+
};
|
16
|
+
this.remove = (key, value) => {
|
17
|
+
if (!this.dict.has(key))
|
18
|
+
return;
|
19
|
+
const array = this.dict.get(key);
|
20
|
+
const index = array.indexOf(value);
|
21
|
+
if (index !== -1)
|
22
|
+
array.splice(index, 1);
|
23
|
+
if (array.length === 0)
|
24
|
+
this.dict.delete(key);
|
25
|
+
};
|
26
|
+
this.getAll = (key) => {
|
27
|
+
var _a;
|
28
|
+
return (_a = this.dict.get(key)) !== null && _a !== void 0 ? _a : [];
|
29
|
+
};
|
30
|
+
this.entries = () => this.dict.entries();
|
31
|
+
this.has = (key) => this.dict.has(key);
|
32
|
+
}
|
33
|
+
}
|
34
|
+
exports.MultiValueMap = MultiValueMap;
|
35
|
+
const useMultiValueMap = () => {
|
36
|
+
const ref = (0, react_1.useRef)(null);
|
37
|
+
return () => {
|
38
|
+
if (ref.current)
|
39
|
+
return ref.current;
|
40
|
+
ref.current = new MultiValueMap();
|
41
|
+
return ref.current;
|
42
|
+
};
|
43
|
+
};
|
44
|
+
exports.useMultiValueMap = useMultiValueMap;
|
@@ -1,4 +1 @@
|
|
1
|
-
import { GenericObject } from "..";
|
2
1
|
export declare const objectFromPathEntries: (entries: [string, any][]) => {};
|
3
|
-
/** Flatten an object so there are no nested objects or arrays */
|
4
|
-
export declare function flatten(obj: GenericObject, preserveEmpty?: boolean): GenericObject;
|
@@ -3,41 +3,12 @@ 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.
|
7
|
-
// `flatten` is taken from https://github.com/richie5um/FlattenJS. Decided to implement them here instead of using that package because this is a core functionality of the library and this will add more flexibility in case we need to change the implementation.
|
8
|
-
const assign_1 = __importDefault(require("lodash/assign"));
|
9
|
-
const isArray_1 = __importDefault(require("lodash/isArray"));
|
10
|
-
const isObject_1 = __importDefault(require("lodash/isObject"));
|
11
|
-
const keys_1 = __importDefault(require("lodash/keys"));
|
12
|
-
const mapKeys_1 = __importDefault(require("lodash/mapKeys"));
|
6
|
+
exports.objectFromPathEntries = void 0;
|
13
7
|
const set_1 = __importDefault(require("lodash/set"));
|
14
|
-
const
|
15
|
-
const objectFromPathEntries = (entries) =>
|
8
|
+
const MultiValueMap_1 = require("./MultiValueMap");
|
9
|
+
const objectFromPathEntries = (entries) => {
|
10
|
+
const map = new MultiValueMap_1.MultiValueMap();
|
11
|
+
entries.forEach(([key, value]) => map.add(key, value));
|
12
|
+
return [...map.entries()].reduce((acc, [key, value]) => (0, set_1.default)(acc, key, value.length === 1 ? value[0] : value), {});
|
13
|
+
};
|
16
14
|
exports.objectFromPathEntries = objectFromPathEntries;
|
17
|
-
/** Flatten an object so there are no nested objects or arrays */
|
18
|
-
function flatten(obj, preserveEmpty = false) {
|
19
|
-
return (0, transform_1.default)(obj, function (result, value, key) {
|
20
|
-
if ((0, isObject_1.default)(value)) {
|
21
|
-
let flatMap = (0, mapKeys_1.default)(flatten(value, preserveEmpty), function (_mvalue, mkey) {
|
22
|
-
if ((0, isArray_1.default)(value)) {
|
23
|
-
let index = mkey.indexOf(".");
|
24
|
-
if (-1 !== index) {
|
25
|
-
return `${key}[${mkey.slice(0, index)}]${mkey.slice(index)}`;
|
26
|
-
}
|
27
|
-
return `${key}[${mkey}]`;
|
28
|
-
}
|
29
|
-
return `${key}.${mkey}`;
|
30
|
-
});
|
31
|
-
(0, assign_1.default)(result, flatMap);
|
32
|
-
// Preverve Empty arrays and objects
|
33
|
-
if (preserveEmpty && (0, keys_1.default)(flatMap).length === 0) {
|
34
|
-
result[key] = value;
|
35
|
-
}
|
36
|
-
}
|
37
|
-
else {
|
38
|
-
result[key] = value;
|
39
|
-
}
|
40
|
-
return result;
|
41
|
-
}, {});
|
42
|
-
}
|
43
|
-
exports.flatten = flatten;
|
@@ -14,8 +14,16 @@ export declare type CreateGetInputPropsOptions = {
|
|
14
14
|
validationBehavior?: Partial<ValidationBehaviorOptions>;
|
15
15
|
name: string;
|
16
16
|
};
|
17
|
-
declare type HandledProps = "name" | "defaultValue";
|
17
|
+
declare type HandledProps = "name" | "defaultValue" | "defaultChecked";
|
18
18
|
declare type Callbacks = "onChange" | "onBlur";
|
19
|
-
|
19
|
+
declare type MinimalInputProps = {
|
20
|
+
onChange?: (...args: any[]) => void;
|
21
|
+
onBlur?: (...args: any[]) => void;
|
22
|
+
defaultValue?: any;
|
23
|
+
defaultChecked?: boolean;
|
24
|
+
name?: string;
|
25
|
+
type?: string;
|
26
|
+
};
|
27
|
+
export declare type GetInputProps = <T extends MinimalInputProps>(props?: Omit<T, HandledProps | Callbacks> & Partial<Pick<T, Callbacks>>) => T;
|
20
28
|
export declare const createGetInputProps: ({ clearError, validate, defaultValue, touched, setTouched, hasBeenSubmitted, validationBehavior, name, }: CreateGetInputPropsOptions) => GetInputProps;
|
21
29
|
export {};
|
@@ -6,18 +6,28 @@ const defaultValidationBehavior = {
|
|
6
6
|
whenTouched: "onChange",
|
7
7
|
whenSubmitted: "onChange",
|
8
8
|
};
|
9
|
+
const getCheckboxDefaultChecked = (value, defaultValue) => {
|
10
|
+
if (Array.isArray(defaultValue))
|
11
|
+
defaultValue.includes(value);
|
12
|
+
if (typeof defaultValue === "boolean")
|
13
|
+
return defaultValue;
|
14
|
+
if (typeof defaultValue === "string")
|
15
|
+
return defaultValue === value;
|
16
|
+
return undefined;
|
17
|
+
};
|
9
18
|
const createGetInputProps = ({ clearError, validate, defaultValue, touched, setTouched, hasBeenSubmitted, validationBehavior, name, }) => {
|
10
19
|
const validationBehaviors = {
|
11
20
|
...defaultValidationBehavior,
|
12
21
|
...validationBehavior,
|
13
22
|
};
|
14
23
|
return (props = {}) => {
|
24
|
+
var _a, _b;
|
15
25
|
const behavior = hasBeenSubmitted
|
16
26
|
? validationBehaviors.whenSubmitted
|
17
27
|
: touched
|
18
28
|
? validationBehaviors.whenTouched
|
19
29
|
: validationBehaviors.initial;
|
20
|
-
const
|
30
|
+
const inputProps = {
|
21
31
|
...props,
|
22
32
|
onChange: (...args) => {
|
23
33
|
var _a;
|
@@ -34,10 +44,20 @@ const createGetInputProps = ({ clearError, validate, defaultValue, touched, setT
|
|
34
44
|
setTouched(true);
|
35
45
|
return (_a = props === null || props === void 0 ? void 0 : props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, ...args);
|
36
46
|
},
|
37
|
-
defaultValue,
|
38
47
|
name,
|
39
48
|
};
|
40
|
-
|
49
|
+
if (inputProps.type === "checkbox") {
|
50
|
+
const value = (_a = props.value) !== null && _a !== void 0 ? _a : "on";
|
51
|
+
inputProps.defaultChecked = getCheckboxDefaultChecked(value, defaultValue);
|
52
|
+
}
|
53
|
+
else if (inputProps.type === "radio") {
|
54
|
+
const value = (_b = props.value) !== null && _b !== void 0 ? _b : "on";
|
55
|
+
inputProps.defaultChecked = defaultValue === value;
|
56
|
+
}
|
57
|
+
else {
|
58
|
+
inputProps.defaultValue = defaultValue;
|
59
|
+
}
|
60
|
+
return inputProps;
|
41
61
|
};
|
42
62
|
};
|
43
63
|
exports.createGetInputProps = createGetInputProps;
|
package/build/internal/test.d.ts
CHANGED
File without changes
|
package/build/internal/test.js
CHANGED
File without changes
|
package/package.json
CHANGED
package/src/ValidatedForm.tsx
CHANGED
@@ -14,10 +14,7 @@ import React, {
|
|
14
14
|
} from "react";
|
15
15
|
import invariant from "tiny-invariant";
|
16
16
|
import { FormContext, FormContextValue } from "./internal/formContext";
|
17
|
-
import {
|
18
|
-
MultiValueMap,
|
19
|
-
useMultiValueMap,
|
20
|
-
} from "./internal/SingleTypeMultiValueMap";
|
17
|
+
import { MultiValueMap, useMultiValueMap } from "./internal/MultiValueMap";
|
21
18
|
import { useSubmitComplete } from "./internal/submissionCallbacks";
|
22
19
|
import { omit, mergeRefs } from "./internal/util";
|
23
20
|
import {
|
@@ -229,13 +226,22 @@ export function ValidatedForm<DataType>({
|
|
229
226
|
getDataFromForm(formRef.current),
|
230
227
|
fieldName as any
|
231
228
|
);
|
229
|
+
|
230
|
+
// By checking and returning `prev` here, we can avoid a re-render
|
231
|
+
// if the validation state is the same.
|
232
232
|
if (error) {
|
233
|
-
setFieldErrors((prev) =>
|
234
|
-
|
235
|
-
|
236
|
-
|
233
|
+
setFieldErrors((prev) => {
|
234
|
+
if (prev[fieldName] === error) return prev;
|
235
|
+
return {
|
236
|
+
...prev,
|
237
|
+
[fieldName]: error,
|
238
|
+
};
|
239
|
+
});
|
237
240
|
} else {
|
238
|
-
setFieldErrors((prev) =>
|
241
|
+
setFieldErrors((prev) => {
|
242
|
+
if (!(fieldName in prev)) return prev;
|
243
|
+
return omit(prev, fieldName);
|
244
|
+
});
|
239
245
|
}
|
240
246
|
},
|
241
247
|
registerReceiveFocus: (fieldName, handler) => {
|
@@ -4,7 +4,6 @@ export class MultiValueMap<Key, Value> {
|
|
4
4
|
private dict: Map<Key, Value[]> = new Map();
|
5
5
|
|
6
6
|
add = (key: Key, value: Value) => {
|
7
|
-
this.dict.set(key, [...(this.dict.get(key) ?? []), value]);
|
8
7
|
if (this.dict.has(key)) {
|
9
8
|
this.dict.get(key)!.push(value);
|
10
9
|
} else {
|
@@ -24,6 +23,8 @@ export class MultiValueMap<Key, Value> {
|
|
24
23
|
return this.dict.get(key) ?? [];
|
25
24
|
};
|
26
25
|
|
26
|
+
entries = (): IterableIterator<[Key, Value[]]> => this.dict.entries();
|
27
|
+
|
27
28
|
has = (key: Key): boolean => this.dict.has(key);
|
28
29
|
}
|
29
30
|
|
package/src/internal/flatten.ts
CHANGED
@@ -1,48 +1,11 @@
|
|
1
|
-
// `flatten` is taken from https://github.com/richie5um/FlattenJS. Decided to implement them here instead of using that package because this is a core functionality of the library and this will add more flexibility in case we need to change the implementation.
|
2
|
-
import assign from "lodash/assign";
|
3
|
-
import isArray from "lodash/isArray";
|
4
|
-
import isObject from "lodash/isObject";
|
5
|
-
import keys from "lodash/keys";
|
6
|
-
import mapKeys from "lodash/mapKeys";
|
7
1
|
import set from "lodash/set";
|
8
|
-
import
|
9
|
-
import { GenericObject } from "..";
|
2
|
+
import { MultiValueMap } from "./MultiValueMap";
|
10
3
|
|
11
|
-
export const objectFromPathEntries = (entries: [string, any][]) =>
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
return transform(
|
17
|
-
obj,
|
18
|
-
function (result: GenericObject, value, key) {
|
19
|
-
if (isObject(value)) {
|
20
|
-
let flatMap = mapKeys(
|
21
|
-
flatten(value, preserveEmpty),
|
22
|
-
function (_mvalue, mkey) {
|
23
|
-
if (isArray(value)) {
|
24
|
-
let index = mkey.indexOf(".");
|
25
|
-
if (-1 !== index) {
|
26
|
-
return `${key}[${mkey.slice(0, index)}]${mkey.slice(index)}`;
|
27
|
-
}
|
28
|
-
return `${key}[${mkey}]`;
|
29
|
-
}
|
30
|
-
return `${key}.${mkey}`;
|
31
|
-
}
|
32
|
-
);
|
33
|
-
|
34
|
-
assign(result, flatMap);
|
35
|
-
|
36
|
-
// Preverve Empty arrays and objects
|
37
|
-
if (preserveEmpty && keys(flatMap).length === 0) {
|
38
|
-
result[key] = value;
|
39
|
-
}
|
40
|
-
} else {
|
41
|
-
result[key] = value;
|
42
|
-
}
|
43
|
-
|
44
|
-
return result;
|
45
|
-
},
|
4
|
+
export const objectFromPathEntries = (entries: [string, any][]) => {
|
5
|
+
const map = new MultiValueMap<string, any>();
|
6
|
+
entries.forEach(([key, value]) => map.add(key, value));
|
7
|
+
return [...map.entries()].reduce(
|
8
|
+
(acc, [key, value]) => set(acc, key, value.length === 1 ? value[0] : value),
|
46
9
|
{}
|
47
10
|
);
|
48
|
-
}
|
11
|
+
};
|
@@ -17,10 +17,19 @@ export type CreateGetInputPropsOptions = {
|
|
17
17
|
name: string;
|
18
18
|
};
|
19
19
|
|
20
|
-
type HandledProps = "name" | "defaultValue";
|
20
|
+
type HandledProps = "name" | "defaultValue" | "defaultChecked";
|
21
21
|
type Callbacks = "onChange" | "onBlur";
|
22
22
|
|
23
|
-
|
23
|
+
type MinimalInputProps = {
|
24
|
+
onChange?: (...args: any[]) => void;
|
25
|
+
onBlur?: (...args: any[]) => void;
|
26
|
+
defaultValue?: any;
|
27
|
+
defaultChecked?: boolean;
|
28
|
+
name?: string;
|
29
|
+
type?: string;
|
30
|
+
};
|
31
|
+
|
32
|
+
export type GetInputProps = <T extends MinimalInputProps>(
|
24
33
|
props?: Omit<T, HandledProps | Callbacks> & Partial<Pick<T, Callbacks>>
|
25
34
|
) => T;
|
26
35
|
|
@@ -30,6 +39,13 @@ const defaultValidationBehavior: ValidationBehaviorOptions = {
|
|
30
39
|
whenSubmitted: "onChange",
|
31
40
|
};
|
32
41
|
|
42
|
+
const getCheckboxDefaultChecked = (value: string, defaultValue: any) => {
|
43
|
+
if (Array.isArray(defaultValue)) defaultValue.includes(value);
|
44
|
+
if (typeof defaultValue === "boolean") return defaultValue;
|
45
|
+
if (typeof defaultValue === "string") return defaultValue === value;
|
46
|
+
return undefined;
|
47
|
+
};
|
48
|
+
|
33
49
|
export const createGetInputProps = ({
|
34
50
|
clearError,
|
35
51
|
validate,
|
@@ -45,14 +61,14 @@ export const createGetInputProps = ({
|
|
45
61
|
...validationBehavior,
|
46
62
|
};
|
47
63
|
|
48
|
-
return <T extends
|
64
|
+
return <T extends MinimalInputProps>(props = {} as any) => {
|
49
65
|
const behavior = hasBeenSubmitted
|
50
66
|
? validationBehaviors.whenSubmitted
|
51
67
|
: touched
|
52
68
|
? validationBehaviors.whenTouched
|
53
69
|
: validationBehaviors.initial;
|
54
70
|
|
55
|
-
const
|
71
|
+
const inputProps: T = {
|
56
72
|
...props,
|
57
73
|
onChange: (...args: unknown[]) => {
|
58
74
|
if (behavior === "onChange") validate();
|
@@ -64,10 +80,22 @@ export const createGetInputProps = ({
|
|
64
80
|
setTouched(true);
|
65
81
|
return props?.onBlur?.(...args);
|
66
82
|
},
|
67
|
-
defaultValue,
|
68
83
|
name,
|
69
84
|
};
|
70
85
|
|
71
|
-
|
86
|
+
if (inputProps.type === "checkbox") {
|
87
|
+
const value = props.value ?? "on";
|
88
|
+
inputProps.defaultChecked = getCheckboxDefaultChecked(
|
89
|
+
value,
|
90
|
+
defaultValue
|
91
|
+
);
|
92
|
+
} else if (inputProps.type === "radio") {
|
93
|
+
const value = props.value ?? "on";
|
94
|
+
inputProps.defaultChecked = defaultValue === value;
|
95
|
+
} else {
|
96
|
+
inputProps.defaultValue = defaultValue;
|
97
|
+
}
|
98
|
+
|
99
|
+
return inputProps;
|
72
100
|
};
|
73
101
|
};
|