From e12bd5fa1e29cbfb29be31020cc49abfd749ca02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20W=C3=BCller?= Date: Thu, 24 Oct 2024 18:18:41 +0200 Subject: [PATCH] Make HTML ID generation fast Replaces HTML ID generation with a simple monotonic sequence to avoid several expensive hash lookups for every generated ID. This entirely eliminates the chance for collisions under regular circumstances, and the default seed even performs better in some adverse circumstances, like multiple instances of the same library sharing an ID namespace on the same page. The only potential downside is that IDs might not be particularly semantically helpful, but that can be mitigated by just appending that information instead. This might be a breaking change if this module is considered a public API. --- .../angular/src/library/abstract-control.ts | 2 - .../src/library/jsonforms.component.ts | 4 +- packages/core/src/util/ids.ts | 37 ++--- packages/core/test/mappers/cell.test.ts | 4 +- packages/core/test/mappers/renderer.test.ts | 4 +- .../src/layouts/ExpandPanelRenderer.tsx | 14 +- packages/react/src/JsonForms.tsx | 150 +++++------------- .../unit/additional/LabelRenderer.spec.ts | 4 +- .../additional/ListWithDetailRenderer.spec.ts | 4 +- .../unit/complex/ArrayControlRenderer.spec.ts | 4 +- .../tests/unit/complex/OneOfRenderer.spec.ts | 4 +- .../controls/BooleanControlRenderer.spec.ts | 4 +- .../unit/controls/DateControlRenderer.spec.ts | 4 +- .../controls/DateTimeControlRenderer.spec.ts | 4 +- .../unit/controls/EnumControlRenderer.spec.ts | 4 +- .../controls/IntegerControlRenderer.spec.ts | 4 +- .../MultiStringControlRenderer.spec.ts | 4 +- .../controls/NumberControlRenderer.spec.ts | 4 +- .../controls/OneOfEnumControlRenderer.spec.ts | 4 +- .../controls/StringControlRenderer.spec.ts | 6 +- .../unit/controls/TimeControlRenderer.spec.ts | 4 +- .../unit/layout/ArrayLayoutRenderer.spec.ts | 4 +- packages/vue/src/jsonFormsCompositions.ts | 11 +- 23 files changed, 95 insertions(+), 193 deletions(-) diff --git a/packages/angular/src/library/abstract-control.ts b/packages/angular/src/library/abstract-control.ts index af43608ce..0dde8f657 100644 --- a/packages/angular/src/library/abstract-control.ts +++ b/packages/angular/src/library/abstract-control.ts @@ -29,7 +29,6 @@ import { JsonFormsState, JsonSchema, OwnPropsOfControl, - removeId, StatePropsOfControl, } from '@jsonforms/core'; import { Component, Input, OnDestroy, OnInit } from '@angular/core'; @@ -149,7 +148,6 @@ export abstract class JsonFormsAbstractControl< ngOnDestroy() { super.ngOnDestroy(); - removeId(this.id); } isEnabled(): boolean { diff --git a/packages/angular/src/library/jsonforms.component.ts b/packages/angular/src/library/jsonforms.component.ts index 22a93264b..6333b03c0 100644 --- a/packages/angular/src/library/jsonforms.component.ts +++ b/packages/angular/src/library/jsonforms.component.ts @@ -32,7 +32,7 @@ import { ViewContainerRef, } from '@angular/core'; import { - createId, + nextId, isControl, getConfig, JsonFormsProps, @@ -143,7 +143,7 @@ export class JsonFormsOutlet const controlInstance = instance as JsonFormsControl; if (controlInstance.id === undefined) { const id = isControl(props.uischema) - ? createId(props.uischema.scope) + ? nextId() + props.uischema.scope : undefined; (instance as JsonFormsControl).id = id; } diff --git a/packages/core/src/util/ids.ts b/packages/core/src/util/ids.ts index 4914754f4..34316019e 100644 --- a/packages/core/src/util/ids.ts +++ b/packages/core/src/util/ids.ts @@ -23,30 +23,21 @@ THE SOFTWARE. */ -const usedIds: Set = new Set(); +let idNamespace: string; +let idIndex: number; -const makeId = (idBase: string, iteration: number) => - iteration <= 1 ? idBase : idBase + iteration.toString(); - -const isUniqueId = (idBase: string, iteration: number) => { - const newID = makeId(idBase, iteration); - return !usedIds.has(newID); -}; - -export const createId = (proposedId: string) => { - if (proposedId === undefined) { - // failsafe to avoid endless loops in error cases - proposedId = 'undefined'; - } - let tries = 0; - while (!isUniqueId(proposedId, tries)) { - tries++; - } - const newID = makeId(proposedId, tries); - usedIds.add(newID); - return newID; +export const seedIds = (namespace = 'jsonforms', index = 0) => { + idNamespace = namespace; + idIndex = index; }; -export const removeId = (id: string) => usedIds.delete(id); +// This has a salt in the unlikely case that someone bundled multiple instances +// of this module on a single site. +seedIds(`jsonforms${Math.random().toString(32).slice(2)}`); -export const clearAllIds = () => usedIds.clear(); +/** + * Generate an ID that's unique within the current execution context. This is + * intended for HTML ID generation. Does not guarantee stability across SSR + * boundaries! + */ +export const nextId = () => `:${idNamespace}:${(idIndex++).toString(36)}:`; diff --git a/packages/core/test/mappers/cell.test.ts b/packages/core/test/mappers/cell.test.ts index c0317aa61..dd940a0d0 100644 --- a/packages/core/test/mappers/cell.test.ts +++ b/packages/core/test/mappers/cell.test.ts @@ -25,7 +25,7 @@ import test from 'ava'; import * as _ from 'lodash'; import * as Redux from 'redux'; -import { clearAllIds, createAjv, validate } from '../../src/util'; +import { seedIds, createAjv, validate } from '../../src/util'; import { UPDATE_DATA, UpdateAction } from '../../src/actions'; import configureStore from 'redux-mock-store'; import { JsonFormsState } from '../../src/store'; @@ -263,7 +263,7 @@ test('mapStateToCellProps - data', (t) => { }); test('mapStateToCellProps - id', (t) => { - clearAllIds(); + seedIds(); const ownProps = { uischema: coreUISchema, id: '#/properties/firstName', diff --git a/packages/core/test/mappers/renderer.test.ts b/packages/core/test/mappers/renderer.test.ts index 8e686e884..6ef1c6975 100644 --- a/packages/core/test/mappers/renderer.test.ts +++ b/packages/core/test/mappers/renderer.test.ts @@ -64,7 +64,7 @@ import { mapStateToOneOfEnumControlProps, mapStateToOneOfProps, } from '../../src/mappers'; -import { clearAllIds, convertDateToString, createAjv } from '../../src/util'; +import { seedIds, convertDateToString, createAjv } from '../../src/util'; import { rankWith } from '../../src'; const middlewares: Redux.Middleware[] = []; @@ -429,7 +429,7 @@ test('mapStateToControlProps - no duplicate error messages', (t) => { }); test('mapStateToControlProps - id', (t) => { - clearAllIds(); + seedIds(); const ownProps = { uischema: coreUISchema, id: '#/properties/firstName', diff --git a/packages/material-renderers/src/layouts/ExpandPanelRenderer.tsx b/packages/material-renderers/src/layouts/ExpandPanelRenderer.tsx index fc02b28e6..6e5add363 100644 --- a/packages/material-renderers/src/layouts/ExpandPanelRenderer.tsx +++ b/packages/material-renderers/src/layouts/ExpandPanelRenderer.tsx @@ -5,8 +5,6 @@ import React, { Fragment, ReducerAction, useMemo, - useState, - useEffect, useCallback, } from 'react'; import { @@ -25,8 +23,7 @@ import { update, JsonFormsCellRendererRegistryEntry, JsonFormsUISchemaRegistryEntry, - createId, - removeId, + nextId, ArrayTranslations, computeChildLabel, UpdateArrayContext, @@ -88,13 +85,8 @@ export interface ExpandPanelProps DispatchPropsOfExpandPanel {} const ExpandPanelRendererComponent = (props: ExpandPanelProps) => { - const [labelHtmlId] = useState(createId('expand-panel')); - - useEffect(() => { - return () => { - removeId(labelHtmlId); - }; - }, [labelHtmlId]); + // TODO: Should probably use React.useId() when support for React < 18 is dropped. + const labelHtmlId = useMemo(() => nextId() + 'expand-panel', []); const { enabled, diff --git a/packages/react/src/JsonForms.tsx b/packages/react/src/JsonForms.tsx index 38ed5514d..4262fa7f8 100644 --- a/packages/react/src/JsonForms.tsx +++ b/packages/react/src/JsonForms.tsx @@ -28,9 +28,8 @@ import type Ajv from 'ajv'; import type { ErrorObject } from 'ajv'; import { UnknownRenderer } from './UnknownRenderer'; import { - createId, + nextId, Generate, - isControl, JsonFormsCellRendererRegistryEntry, JsonFormsCore, JsonFormsI18nState, @@ -40,7 +39,6 @@ import { JsonSchema, Middleware, OwnPropsOfJsonFormsRenderer, - removeId, UISchemaElement, ValidationMode, } from '@jsonforms/core'; @@ -49,129 +47,57 @@ import { withJsonFormsRendererProps, } from './JsonFormsContext'; -interface JsonFormsRendererState { - id: string; -} - export interface JsonFormsReactProps { onChange?(state: Pick): void; middleware?: Middleware; } -export class JsonFormsDispatchRenderer extends React.Component< - JsonFormsProps, - JsonFormsRendererState -> { - constructor(props: JsonFormsProps) { - super(props); - this.state = { - id: isControl(props.uischema) - ? createId(props.uischema.scope) - : undefined, - }; - } - - componentWillUnmount() { - if (isControl(this.props.uischema)) { - removeId(this.state.id); - } - } - - componentDidUpdate(prevProps: JsonFormsProps) { - if (prevProps.schema !== this.props.schema) { - removeId(this.state.id); - this.setState({ - id: isControl(this.props.uischema) - ? createId(this.props.uischema.scope) - : undefined, - }); - } - } - - render() { - const { - schema, - rootSchema, - uischema, - path, - enabled, - renderers, - cells, - config, - } = this.props as JsonFormsProps; - - return ( - +export const JsonFormsDispatchRenderer = React.memo( + function JsonFormsDispatchRenderer(props: JsonFormsProps) { + // TODO: Should probably use React.useId() when support for React < 18 is dropped. + const id = useMemo(nextId, [props.schema]); + const testerContext = useMemo( + () => ({ + rootSchema: props.rootSchema, + config: props.config, + }), + [props.rootSchema, props.config] ); - } -} - -const TestAndRender = React.memo(function TestAndRender(props: { - uischema: UISchemaElement; - schema: JsonSchema; - rootSchema: JsonSchema; - path: string; - enabled: boolean; - renderers: JsonFormsRendererRegistryEntry[]; - cells: JsonFormsCellRendererRegistryEntry[]; - id: string; - config: any; -}) { - const testerContext = useMemo( - () => ({ - rootSchema: props.rootSchema, - config: props.config, - }), - [props.rootSchema, props.config] - ); - const renderer = useMemo( - () => - maxBy(props.renderers, (r) => - r.tester(props.uischema, props.schema, testerContext) - ), - [props.renderers, props.uischema, props.schema, testerContext] - ); - if ( - renderer === undefined || - renderer.tester(props.uischema, props.schema, testerContext) === -1 - ) { - return ; - } else { - const Render = renderer.renderer; - return ( - + const renderer = useMemo( + () => + maxBy(props.renderers, (r) => + r.tester(props.uischema, props.schema, testerContext) + ), + [props.renderers, props.uischema, props.schema, testerContext] ); + if ( + renderer === undefined || + renderer.tester(props.uischema, props.schema, testerContext) === -1 + ) { + return ; + } else { + const Render = renderer.renderer; + return ( + + ); + } } -}); +); /** * @deprecated Since Version 3.0 this optimization renderer is no longer necessary. * Use `JsonFormsDispatch` instead. * We still export it for backward compatibility */ -export class ResolvedJsonFormsDispatchRenderer extends JsonFormsDispatchRenderer { - constructor(props: JsonFormsProps) { - super(props); - } -} +export const ResolvedJsonFormsDispatchRenderer = JsonFormsDispatchRenderer; export const JsonFormsDispatch: ComponentType = withJsonFormsRendererProps(JsonFormsDispatchRenderer); diff --git a/packages/vue-vuetify/tests/unit/additional/LabelRenderer.spec.ts b/packages/vue-vuetify/tests/unit/additional/LabelRenderer.spec.ts index c7220b47c..35e0d4fc0 100644 --- a/packages/vue-vuetify/tests/unit/additional/LabelRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/additional/LabelRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import LabelRenderer from '../../../src/additional/LabelRenderer.vue'; import { entry as labelRendererEntry } from '../../../src/additional/LabelRenderer.entry'; import { mountJsonForms } from '../util'; @@ -20,7 +20,7 @@ describe('LabelRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/additional/ListWithDetailRenderer.spec.ts b/packages/vue-vuetify/tests/unit/additional/ListWithDetailRenderer.spec.ts index 190b5ff49..25a9e1e65 100644 --- a/packages/vue-vuetify/tests/unit/additional/ListWithDetailRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/additional/ListWithDetailRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds, type Translator } from '@jsonforms/core'; +import { seedIds, type Translator } from '@jsonforms/core'; import ListWithDetailRenderer from '../../../src/additional/ListWithDetailRenderer.vue'; import { entry as listWithDetailRendererEntry } from '../../../src/additional/ListWithDetailRenderer.entry'; import { mountJsonForms } from '../util'; @@ -28,7 +28,7 @@ describe('ListWithDetailRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema, undefined, { translate: ((id, defaultMessage) => { if (id.endsWith('addAriaLabel')) { diff --git a/packages/vue-vuetify/tests/unit/complex/ArrayControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/complex/ArrayControlRenderer.spec.ts index 0422ca64f..a45cca58a 100644 --- a/packages/vue-vuetify/tests/unit/complex/ArrayControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/complex/ArrayControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds, type Translator } from '@jsonforms/core'; +import { seedIds, type Translator } from '@jsonforms/core'; import ArrayControlRenderer from '../../../src/complex/ArrayControlRenderer.vue'; import { entry as arrayControlRendererEntry } from '../../../src/complex/ArrayControlRenderer.entry'; import { mountJsonForms } from '../util'; @@ -27,7 +27,7 @@ describe('ArrayControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema, undefined, { translate: ((id, defaultMessage) => { if (id.endsWith('addAriaLabel')) { diff --git a/packages/vue-vuetify/tests/unit/complex/OneOfRenderer.spec.ts b/packages/vue-vuetify/tests/unit/complex/OneOfRenderer.spec.ts index 9cae6a50e..ee2349a23 100644 --- a/packages/vue-vuetify/tests/unit/complex/OneOfRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/complex/OneOfRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds, type Translator } from '@jsonforms/core'; +import { seedIds, type Translator } from '@jsonforms/core'; import OneOfControlRenderer from '../../../src/complex/OneOfRenderer.vue'; import { entry as oneOfControlRendererEntry } from '../../../src/complex/OneOfRenderer.entry'; import { mountJsonForms } from '../util'; @@ -38,7 +38,7 @@ describe('OneOfRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema, undefined, { translate: ((id, defaultMessage) => { if (id.endsWith('clearDialogAccept')) { diff --git a/packages/vue-vuetify/tests/unit/controls/BooleanControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/BooleanControlRenderer.spec.ts index 1c1c592a0..08e1ef2d2 100644 --- a/packages/vue-vuetify/tests/unit/controls/BooleanControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/BooleanControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import BooleanControlRenderer from '../../../src/controls/BooleanControlRenderer.vue'; import { entry as booleanControlRendererEntry } from '../../../src/controls/BooleanControlRenderer.entry'; import { mountJsonForms } from '../util'; @@ -23,7 +23,7 @@ describe('BooleanControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/DateControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/DateControlRenderer.spec.ts index 4365a8486..04e6484dd 100644 --- a/packages/vue-vuetify/tests/unit/controls/DateControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/DateControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import DateControlRenderer from '../../../src/controls/DateControlRenderer.vue'; import { entry as dateControlRendererEntry } from '../../../src/controls/DateControlRenderer.entry'; import { mountJsonForms } from '../util'; @@ -27,7 +27,7 @@ describe('DateControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/DateTimeControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/DateTimeControlRenderer.spec.ts index 08f994643..05ec9947b 100644 --- a/packages/vue-vuetify/tests/unit/controls/DateTimeControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/DateTimeControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import DateTimeControlRenderer from '../../../src/controls/DateTimeControlRenderer.vue'; import { entry as dateTimeControlRendererEntry } from '../../../src/controls/DateTimeControlRenderer.entry'; import { mountJsonForms } from '../util'; @@ -26,7 +26,7 @@ describe('DateTimeControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/EnumControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/EnumControlRenderer.spec.ts index 9f9c19c33..9c391e010 100644 --- a/packages/vue-vuetify/tests/unit/controls/EnumControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/EnumControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import EnumControlRenderer from '../../../src/controls/EnumControlRenderer.vue'; import { entry as enumControlRendererEntry } from '../../../src/controls/EnumControlRenderer.entry'; import { wait } from '../../../tests'; @@ -24,7 +24,7 @@ describe('EnumControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/IntegerControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/IntegerControlRenderer.spec.ts index ef64f124a..88c01f448 100644 --- a/packages/vue-vuetify/tests/unit/controls/IntegerControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/IntegerControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import IntegerControlRenderer from '../../../src/controls/IntegerControlRenderer.vue'; import { entry as integerControlRendererEntry } from '../../../src/controls/IntegerControlRenderer.entry'; import { wait } from '../../../tests'; @@ -24,7 +24,7 @@ describe('IntegerControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/MultiStringControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/MultiStringControlRenderer.spec.ts index 77a6b14e9..3c6703acc 100644 --- a/packages/vue-vuetify/tests/unit/controls/MultiStringControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/MultiStringControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import MultiStringControlRenderer from '../../../src/controls/MultiStringControlRenderer.vue'; import { entry as multiStringControlRendererEntry } from '../../../src/controls/MultiStringControlRenderer.entry'; import { wait } from '../../../tests'; @@ -25,7 +25,7 @@ describe('MultiStringControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/NumberControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/NumberControlRenderer.spec.ts index 0f76ffd83..8121e50a4 100644 --- a/packages/vue-vuetify/tests/unit/controls/NumberControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/NumberControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import NumberControlRenderer from '../../../src/controls/NumberControlRenderer.vue'; import { entry as numberControlRendererEntry } from '../../../src/controls/NumberControlRenderer.entry'; import { wait } from '../../../tests'; @@ -23,7 +23,7 @@ describe('NumberControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/OneOfEnumControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/OneOfEnumControlRenderer.spec.ts index f8f293037..2942b31fb 100644 --- a/packages/vue-vuetify/tests/unit/controls/OneOfEnumControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/OneOfEnumControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import OneOfEnumControlRenderer from '../../../src/controls/OneOfEnumControlRenderer.vue'; import { entry as oneOfEnumControlRendererEntry } from '../../../src/controls/OneOfEnumControlRenderer.entry'; import { wait } from '../../../tests'; @@ -26,7 +26,7 @@ describe('OneOfEnumControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/StringControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/StringControlRenderer.spec.ts index 345f9287d..39bfa75af 100644 --- a/packages/vue-vuetify/tests/unit/controls/StringControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/StringControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import StringControlRenderer from '../../../src/controls/StringControlRenderer.vue'; import { entry as stringControlRendererEntry } from '../../../src/controls/StringControlRenderer.entry'; import { wait } from '../../../tests'; @@ -25,7 +25,7 @@ describe('StringControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); @@ -91,7 +91,7 @@ describe('StringControlRenderer.vue with suggestion', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/TimeControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/TimeControlRenderer.spec.ts index 556200c44..d9cd8b871 100644 --- a/packages/vue-vuetify/tests/unit/controls/TimeControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/TimeControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import TimeControlRenderer from '../../../src/controls/TimeControlRenderer.vue'; import { entry as timeControlRendererEntry } from '../../../src/controls/TimeControlRenderer.entry'; import { mountJsonForms } from '../util'; @@ -25,7 +25,7 @@ describe('TimeControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/layout/ArrayLayoutRenderer.spec.ts b/packages/vue-vuetify/tests/unit/layout/ArrayLayoutRenderer.spec.ts index ba8a591c5..fa1d684f1 100644 --- a/packages/vue-vuetify/tests/unit/layout/ArrayLayoutRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/layout/ArrayLayoutRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds, type Translator } from '@jsonforms/core'; +import { seedIds, type Translator } from '@jsonforms/core'; import ArrayLayoutRenderer from '../../../src/layouts/ArrayLayoutRenderer.vue'; import { entry as arrayLayoutRendererEntry } from '../../../src/layouts/ArrayLayoutRenderer.entry'; import { mountJsonForms } from '../util'; @@ -28,7 +28,7 @@ describe('ArrayLayoutRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema, undefined, { translate: ((id, defaultMessage) => { if (id.endsWith('addAriaLabel')) { diff --git a/packages/vue/src/jsonFormsCompositions.ts b/packages/vue/src/jsonFormsCompositions.ts index 1ac967aab..dbb366230 100644 --- a/packages/vue/src/jsonFormsCompositions.ts +++ b/packages/vue/src/jsonFormsCompositions.ts @@ -29,8 +29,7 @@ import { mapStateToDispatchCellProps, mapStateToOneOfEnumCellProps, StatePropsOfJsonFormsRenderer, - createId, - removeId, + nextId, mapStateToMultiEnumControlProps, mapDispatchToMultiEnumProps, mapStateToLabelProps, @@ -197,7 +196,7 @@ export function useControl< onBeforeMount(() => { if (control.value.uischema.scope) { - id.value = createId(control.value.uischema.scope); + id.value = nextId() + control.value.uischema.scope; } }); @@ -205,17 +204,13 @@ export function useControl< () => props.schema, (newSchema, prevSchem) => { if (newSchema !== prevSchem && isControl(control.value.uischema)) { - if (id.value) { - removeId(id.value); - } - id.value = createId(control.value.uischema.scope); + id.value = nextId() + control.value.uischema.scope; } } ); onUnmounted(() => { if (id.value) { - removeId(id.value); id.value = undefined; } });