fix(vue): keep renderer bindings stable when they do not change - #2634
KiaraGrouwstra wants to merge 3 commits into
Conversation
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
✅ Deploy Preview for jsonforms-examples ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
EclipseSourceAI
left a comment
There was a problem hiding this comment.
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
vuetests 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.
`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
Fixes #2633.
Each change of the JSON Forms state recomputes the
controlbindings ofuseControland therendererbindings ofuseJsonFormsRenderer. 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,schemaandrootSchema: 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.The structural compare looks only into arrays and plain objects. It compares other values (for example
Date,MapandSet) 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.DispatchRendererforwarded its slots withv-for. Vue then marks the slots as dynamic and always updates the child, also when its props are equal.DispatchRenderernow forwards slots only when it has slots.Tests:
packages/vue/tests/unit/stableBindings.spec.tscounts 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 changedDatein the UI schema options causes a render. Each test fails without its fix. Thevue,vue-vanillaandvue-vuetifytest suites pass.We use the same change as a build-time patch of
@jsonforms/vue3.7.0 in https://git.fediversity.eu/fediversity/nix-module-form (seepatches/jsonforms-vue-stable-control.nix).Assisted-by: Claude:claude-opus-5-5
Claude-Session: https://claude.ai/code/session_01DngH17MDYXHUMa68WjMzRo