From fc6a5ae4eb910c807b9eebd231de831561d8eae3 Mon Sep 17 00:00:00 2001
From: Heath C <51679588+heath-freenome@users.noreply.github.com>
Date: Fri, 9 Dec 2022 09:43:02 -0800
Subject: [PATCH] fix: Prevent duplicate extraErrors when not live validating
(#3288)
* fix: Prevent duplicated extraErrors when not live validating
Fixed #3169 by merging `extraErrors` in while preventing duplicates
- Updated `@rjsf/utils` to make the `mergeObjects()` function support a `preventDuplicates` mode when concatenating arrays
- Updated the tests to verify this new feature
- Updated `@rjsf/core` to use the `preventDuplicates` mode when merging `extraErrors` when not live validating
- Updated `validation.md` to remove extraneous leading space to fix #3282
- Updated `utility-functions.md` to document the new mode
- Updated `index.md` to provide the proper `core.cjs.production.min.js` package name for `unpkg.com` fixing #3262
- Updated the `CHANGELOG.md` accordingly
* - Responded to reviewer feedback
* - Fixed url for unpkg directory
---
CHANGELOG.md | 7 +++++++
docs/api-reference/utility-functions.md | 4 ++--
docs/index.md | 6 +++---
docs/usage/validation.md | 4 ++--
packages/core/src/components/Form.tsx | 6 +++++-
packages/utils/src/mergeObjects.ts | 17 ++++++++++++++---
packages/utils/test/mergeObjects.test.ts | 18 ++++++++++++++++++
7 files changed, 51 insertions(+), 11 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 194905b6dc..f27151110f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,10 +22,17 @@ should change the heading of the (upcoming) version to include a major version b
## @rjsf/core
- Added `ref` definition to `ThemeProps` fixing [#2135](https://github.com/rjsf-team/react-jsonschema-form/issues/2135)
+- Updated the `onChange` handler in `Form` to use the new `preventDuplicates` mode of `mergeObjects()` when merging `extraErrors` when live validation is off, fixing [#3169](https://github.com/rjsf-team/react-jsonschema-form/issues/3169)
## @rjsf/utils
- Updated `computedDefaults` (used by `getDefaultFormState`) to skip saving the computed default if it's an empty object unless `includeUndefinedValues` is truthy, fixing [#2150](https://github.com/rjsf-team/react-jsonschema-form/issues/2150) and [#2708](https://github.com/rjsf-team/react-jsonschema-form/issues/2708)
- Expanded the `getDefaultFormState` util's `includeUndefinedValues` prop to accept a boolean or `"excludeObjectChildren"` if it does not want to include undefined values in nested objects
+- Updated `mergeObjects` to add new `preventDuplicates` mode when concatenating arrays so that only unique values from the source object array are copied to the destination object array
+
+## Dev / docs / playground
+- Removed extraneous leading space on the examples in the validation documentation, fixing [#3282](https://github.com/rjsf-team/react-jsonschema-form/issues/3282)
+- Updated the documentation for `mergeObjects()` for the new `preventDuplicates` mode of concatenating arrays
+- Updated the documentation for unpkg releases to the correct name fixing the confusion found in [#3262](https://github.com/rjsf-team/react-jsonschema-form/issues/3262)
# 5.0.0-beta.13
diff --git a/docs/api-reference/utility-functions.md b/docs/api-reference/utility-functions.md
index 8448288ef9..e57cf92626 100644
--- a/docs/api-reference/utility-functions.md
+++ b/docs/api-reference/utility-functions.md
@@ -257,14 +257,14 @@ Recursively merge deeply nested objects.
#### Parameters
- obj1: GenericObjectType - The first object to merge
- obj2: GenericObjectType - The second object to merge
-- [concatArrays=false]: boolean - Optional flag that, when true, will cause arrays to be concatenated
+- [concatArrays=false]: boolean | "preventDuplicates" - Optional flag that, when true, will cause arrays to be concatenated. Use "preventDuplicates" to merge arrays in a manner that prevents any duplicate entries from being merged.
#### Returns
@returns - A new object that is the merge of the two given objects
### mergeSchemas()
Recursively merge deeply nested schemas.
-The difference between mergeSchemas and mergeObjects is that mergeSchemas only concats arrays for values under the 'required' keyword, and when it does, it doesn't include duplicate values.
+The difference between mergeSchemas and mergeObjects is that mergeSchemas only concats arrays for values under the 'required' keyword, and when it does, it doesn't include duplicate values. NOTE: Uses shallow comparison for the duplicate checking.
#### Parameters
- obj1: GenericObjectType - The first object to merge
diff --git a/docs/index.md b/docs/index.md
index e5022f1d41..4dba752c6b 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -40,12 +40,12 @@ Our latest version requires React 16+. You can also install `react-jsonschema-fo
### As a script served from a CDN
```html
-
+
```
-Source maps are available at [this url](https://unpkg.com/@rjsf/core/dist/react-jsonschema-form.js.map).
+Source maps are available at [this url](https://unpkg.com/@rjsf/core/dist/core.cjs.production.min.js.map).
-> Note: The CDN version **does not** embed `react` or `react-dom`.
+> Note: The CDN version **does not** embed `react` or `react-dom`. If you want other distributions (i.e. umd, esm), look [here](https://unpkg.com/@rjsf/core/dist/) for all releases
You'll also need to alias the default export property to use the Form component:
diff --git a/docs/usage/validation.md b/docs/usage/validation.md
index 46aca1d0da..6323c99acc 100644
--- a/docs/usage/validation.md
+++ b/docs/usage/validation.md
@@ -496,7 +496,7 @@ NOTE: The `ajv-i18n` validators implement the `Localizer` interface.
Using a specific locale while including all of `ajv-i18n`:
- ```tsx
+```tsx
import { RJSFSchema } from "@rjsf/utils";
import { customizeValidator } from '@rjsf/validator-ajv8';
import localizer from "ajv-i18n";
@@ -514,7 +514,7 @@ render((
Using a specific locale minimizing the bundle size
- ```tsx
+```tsx
import { RJSFSchema } from "@rjsf/utils";
import { customizeValidator } from '@rjsf/validator-ajv8';
import spanishLocalizer from "ajv-i18n/localize/es";
diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx
index b4b916201f..8487032251 100644
--- a/packages/core/src/components/Form.tsx
+++ b/packages/core/src/components/Form.tsx
@@ -581,7 +581,11 @@ export default class Form<
};
} else if (!noValidate && newErrorSchema) {
const errorSchema = extraErrors
- ? (mergeObjects(newErrorSchema, extraErrors, true) as ErrorSchema)
+ ? (mergeObjects(
+ newErrorSchema,
+ extraErrors,
+ "preventDuplicates"
+ ) as ErrorSchema)
: newErrorSchema;
state = {
formData: newFormData,
diff --git a/packages/utils/src/mergeObjects.ts b/packages/utils/src/mergeObjects.ts
index ef8896ec30..de8e597f79 100644
--- a/packages/utils/src/mergeObjects.ts
+++ b/packages/utils/src/mergeObjects.ts
@@ -5,13 +5,15 @@ import { GenericObjectType } from "./types";
*
* @param obj1 - The first object to merge
* @param obj2 - The second object to merge
- * @param [concatArrays=false] - Optional flag that, when true, will cause arrays to be concatenated
+ * @param [concatArrays=false] - Optional flag that, when true, will cause arrays to be concatenated. Use
+ * "preventDuplicates" to merge arrays in a manner that prevents any duplicate entries from being merged.
+ * NOTE: Uses shallow comparison for the duplicate checking.
* @returns - A new object that is the merge of the two given objects
*/
export default function mergeObjects(
obj1: GenericObjectType,
obj2: GenericObjectType,
- concatArrays = false
+ concatArrays: boolean | "preventDuplicates" = false
) {
return Object.keys(obj2).reduce((acc, key) => {
const left = obj1 ? obj1[key] : {},
@@ -19,7 +21,16 @@ export default function mergeObjects(
if (obj1 && key in obj1 && isObject(right)) {
acc[key] = mergeObjects(left, right, concatArrays);
} else if (concatArrays && Array.isArray(left) && Array.isArray(right)) {
- acc[key] = left.concat(right);
+ let toMerge = right;
+ if (concatArrays === "preventDuplicates") {
+ toMerge = right.reduce((result, value) => {
+ if (!left.includes(value)) {
+ result.push(value);
+ }
+ return result;
+ }, []);
+ }
+ acc[key] = left.concat(toMerge);
} else {
acc[key] = right;
}
diff --git a/packages/utils/test/mergeObjects.test.ts b/packages/utils/test/mergeObjects.test.ts
index b7957d9637..040e1471f4 100644
--- a/packages/utils/test/mergeObjects.test.ts
+++ b/packages/utils/test/mergeObjects.test.ts
@@ -87,5 +87,23 @@ describe("mergeObjects()", () => {
a: { b: [1, 2] },
});
});
+
+ it("should not concat duplicate values in arrays when concatArrays is 'preventDuplicates'", () => {
+ const obj1 = { a: [1] };
+ const obj2 = { a: [1, 2] };
+
+ expect(mergeObjects(obj1, obj2, "preventDuplicates")).toEqual({
+ a: [1, 2],
+ });
+ });
+
+ it("should not concat duplicate values in nested arrays when concatArrays is 'preventDuplicates'", () => {
+ const obj1 = { a: { b: [1] } };
+ const obj2 = { a: { b: [1, 2] } };
+
+ expect(mergeObjects(obj1, obj2, "preventDuplicates")).toEqual({
+ a: { b: [1, 2] },
+ });
+ });
});
});