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] }, + }); + }); }); });