Skip to content

fix(vue): keep renderer bindings stable when they do not change - #2634

Open
KiaraGrouwstra wants to merge 3 commits into
eclipsesource:masterfrom
KiaraGrouwstra:vue-stable-computed
Open

KiaraGrouwstra wants to merge 3 commits into
eclipsesource:masterfrom
KiaraGrouwstra:vue-stable-computed

Conversation

@KiaraGrouwstra

@KiaraGrouwstra KiaraGrouwstra commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #2633.

Each change of the JSON Forms state recomputes the control bindings of useControl and the renderer bindings of useJsonFormsRenderer. Both getters returned a new object each time, so one edit re-rendered every renderer in the form. On forms with some hundred controls, this gives a delay of hundreds of milliseconds for each keystroke.

The two computed values now use stableComputed. Its getter receives the previous value (Vue 3.4 and later) and returns the previous object when the new object has the same keys and equal values:

  • data, schema and rootSchema: compared by identity. The core replaces data immutably, so changed data always has a new identity. A structural compare of the schemas would be expensive.
  • uischema: compared structurally, to depth 8. Renderers often give a new clone of a UI schema element to a nested dispatch on each render.
  • All other keys: compared structurally, to depth 3.

The structural compare looks only into arrays and plain objects. It compares other values (for example Date, Map and Set) by identity. Thus it finds two values equal only when they are deep-equal, so a renderer cannot get stale bindings. A missed match costs only the render that occurs without this change.

DispatchRenderer forwarded its slots with v-for. Vue then marks the slots as dynamic and always updates the child, also when its props are equal. DispatchRenderer now forwards slots only when it has slots.

Tests: packages/vue/tests/unit/stableBindings.spec.ts counts the renders of two controls. When one control changes, the other control does not render again, also when a layout dispatches a cloned UI schema element. A further test makes sure that a changed Date in the UI schema options causes a render. Each test fails without its fix. The vue, vue-vanilla and vue-vuetify test suites pass.

We use the same change as a build-time patch of @jsonforms/vue 3.7.0 in https://git.fediversity.eu/fediversity/nix-module-form (see patches/jsonforms-vue-stable-control.nix).

Assisted-by: Claude:claude-opus-5-5

Claude-Session: https://claude.ai/code/session_01DngH17MDYXHUMa68WjMzRo

Each change of the JSON Forms state recomputes the `control` bindings of
`useControl` and the `renderer` bindings of `useJsonFormsRenderer`. Both
getters return a new object each time, also when all the values in it are
the same. Vue compares a computed value by identity, thus one edit re-renders
all the renderers in the form. On forms with some hundred controls, this
gives a delay of hundreds of milliseconds for each keystroke.

The computed getters now return the previous bindings object when the new
object has the same keys and equal values. `data`, `schema` and `rootSchema`
are compared by identity. The other values are compared structurally to a
limited depth.

Assisted-by: Claude:claude-opus-5-5
Claude-Session: https://claude.ai/code/session_01DngH17MDYXHUMa68WjMzRo
@netlify

netlify Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for jsonforms-examples ready!

Name Link
🔨 Latest commit 2ecf3f4
🔍 Latest deploy log https://app.netlify.com/projects/jsonforms-examples/deploys/6abd3722abc3fb000842b88e
😎 Deploy Preview https://deploy-preview-2634--jsonforms-examples.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@sdirix

sdirix commented Sep 30, 2026

Copy link
Copy Markdown
Member

@EclipseSourceAI

@EclipseSourceAI EclipseSourceAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Autonomous AI review.

This review was done by an AI agent and therefore may contain mistakes. Feel free to ignore any comment you disagree with. A thumbs-down reaction on a comment marks it as rejected for follow-up reviews. Noting why in a reply helps, since replies are read too.

Resolving all AI comments does not lead to an automatic approval. A maintainer still needs to review and sign off on the overall architecture and design.

To get an updated review after pushing changes, a maintainer may re-request a review from this account.

Running in Eclipse Enclave, submitted via review-guard-mcp

The PR makes the control bindings of useControl and the renderer bindings of useJsonFormsRenderer stable. It uses a Vue 3.4+ computed that gets the previous value and returns the previous object when the new bindings are equal. data, schema and rootSchema are compared by identity, uischema structurally to depth 8, and all other keys to depth 3. This stops every edit from re-rendering every renderer. The peer dependency is vue ^3.5.0, so the getter argument is supported.

What I checked:

  • The vue tests pass locally, including the new spec.
  • I ran a local check where a rule on one control depends on another. The dependent control still updates.
  • I also ran a check that reproduces the equality bug described below.
  • UI testing in the browser was not possible, since the sandbox could not reach the deploy preview.

Main point for maintainers: the equality helper treats Date, RegExp, Map and Set values with different contents as equal. A replaced uischema or config that only differs in such a value then leaves the renderers stale. The rest is test hygiene: markRaw warnings, and no coverage yet for the cloned uischema case or for useJsonFormsRenderer.

Comment thread packages/vue/src/jsonFormsCompositions.ts
Comment thread packages/vue/tests/unit/stableBindings.spec.ts Outdated
Comment thread packages/vue/tests/unit/stableBindings.spec.ts
`Date`, `RegExp`, `Map` and `Set` keep their contents out of their own keys. The structural compare found two such values equal when their keys were equal, thus a changed date in a UI schema option gave stale bindings. The compare now uses identity for all objects that are not plain objects or arrays.

The spec now marks its renderers raw, to prevent Vue warnings about reactive components.

Assisted-by: Claude:claude-opus-5-5
Claude-Session: https://claude.ai/code/session_01DngH17MDYXHUMa68WjMzRo
`DispatchRenderer` forwards its slots with a `v-for`. Vue marks such slots as dynamic, and renders the child again each time the dispatcher renders, also when the bindings of the child did not change. This occurs when a parent gives a new clone of a UI schema element to the dispatch. The dispatcher now binds no slots when it has none.

Assisted-by: Claude:claude-opus-5-5
Claude-Session: https://claude.ai/code/session_01DngH17MDYXHUMa68WjMzRo

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[vue] one change re-renders every renderer in the form

3 participants