Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ If you apply custom styling that relies on the previous `Grid`-based DOM structu
Furthermore, if you use exported method `renderLayoutElements`, it no longer wraps children in `Grid` items for direction `column`.
This should not affect you except if you explicitly use this method in custom renderers.

### Vue keeps the state of a form shallow

`JsonForms` of `@jsonforms/vue` now keeps its state shallow.
Vue no longer makes a proxy for each node of the `schema`, the `uischema`, the `renderers`, the `cells` and the form data, which makes a large form much faster to mount.

The form unwraps a proxy of the `data`, the `schema`, the `uischema` and `ajv` with `toRaw`, and gives the other props to the state as they are.
JSON Forms writes no flag on the objects of the application.
An application that keeps the `renderers`, the `cells` or the `uischemas` out of deep reactive state thus no longer needs `markRaw` for them, and Vue no longer gives the "received a Component that was made a reactive object" warning for a renderer set.
An application that keeps them in `reactive()` state gives a proxy of them to the form.
Use `shallowRef`, `shallowReactive` or `markRaw` for a renderer set, for which Vue gives the warning above.

The form data that a renderer gets is no longer a proxy.
A change of the data thus needs a `dispatch` of an `update` action, which the `useJsonFormsControl` composition and its relatives do for you through `handleChange`.
A custom renderer that writes into the data object directly, for example with a `v-model` on a nested field, must use `handleChange` instead.

## Migrating to JSON Forms 3.8

### `Translator` type changed from overloaded signatures to a generic conditional type
Expand Down
4 changes: 1 addition & 3 deletions packages/vue-vuetify/dev/validate/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Options } from 'ajv';
import { markRaw } from 'vue';
import { createAjv as createDefaultAjv } from '../../src';
import { ajvKeywords } from './keywords';

Expand All @@ -13,6 +12,5 @@ export const createAjv = () => {
const ajv = createDefaultAjv(options);
ajvKeywords(ajv);

// when ajv is used in component properties do not make it reactive
return markRaw(ajv);
return ajv;
};
7 changes: 1 addition & 6 deletions packages/vue-vuetify/dev/views/ExampleView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import cloneDeep from 'lodash/cloneDeep';
import find from 'lodash/find';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import {
markRaw,
onMounted,
provide,
ref,
Expand Down Expand Up @@ -66,7 +65,7 @@ const initialState = (exampleProp: ExampleDescription) => {

// Get custom renderers for this example (if any)
const customRenderers = getCustomRenderersForExample(example.name);
const renderers = markRaw([...customRenderers, ...extendedVuetifyRenderers]);
const renderers = [...customRenderers, ...extendedVuetifyRenderers];

return {
data: example.data,
Expand Down Expand Up @@ -284,10 +283,6 @@ const handleAction = (action: Action) => {
if (action) {
const newState = action.apply(state);
if (newState) {
if (newState.renderers) {
newState.renderers = markRaw(newState.renderers);
}

Object.assign(state, newState);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ import { IsDynamicPropertyContext } from '@/util/inject';
import {
computed,
defineComponent,
markRaw,
provide,
ref,
unref,
Expand Down Expand Up @@ -433,12 +432,12 @@ export default defineComponent({

return {
validationMode: validationMode,
i18n: i18n ? markRaw(i18n) : i18n,
middleware: middleware ? markRaw(middleware) : middleware,
i18n,
middleware,
t,
mdAndUp,
vuetifyProps,
ajv: ajv ? markRaw(ajv) : ajv,
ajv,
control,
styles,
appliedOptions,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { clearAllIds, createAjv } from '@jsonforms/core';
import { markRaw } from 'vue';
import { extendedVuetifyRenderers } from '../../../src';
import { mountJsonForms } from '../util/util';

Expand All @@ -27,7 +26,7 @@ describe('AdditionalProperties nested AJV', () => {

it('mounts a map whose key pattern is only valid without the `u` flag', () => {
// A parent form configured with `unicodeRegExp: false`
const ajv = markRaw(createAjv({ unicodeRegExp: false }));
const ajv = createAjv({ unicodeRegExp: false });
expect(() =>
mountJsonForms(
{ secretFiles: {} },
Expand Down
56 changes: 41 additions & 15 deletions packages/vue/src/components/JsonForms.vue
Comment thread
KiaraGrouwstra marked this conversation as resolved.
Comment thread
KiaraGrouwstra marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,14 @@ import {
ValidationMode,
} from '@jsonforms/core';
import isEqual from 'lodash/isEqual';
import { defineComponent, PropType, reactive, toRaw } from 'vue';
import {
defineComponent,
PropType,
reactive,
shallowReactive,
shallowRef,
toRaw,
} from 'vue';
import { JsonFormsChangeEvent, MaybeReadonly } from '../types';
import DispatchRenderer from './DispatchRenderer.vue';

Expand Down Expand Up @@ -120,11 +127,25 @@ export default defineComponent({
},
},
emits: ['change', 'update:data'],
/**
* The state of a form is shallow.
*
* JSON Forms only reads the schema and the UI schema, thus Vue does not need
* to make them reactive. A deep proxy made a new proxy for each nested schema
* node at the first read of that node. Testers and the scope resolution read
* the schema many times for each control, thus these proxies made a large
* form slow to mount.
*
* `toRaw` unwraps a proxy of the data, the schema and the UI schema, thus the
* form always works on the plain objects. `shallowRef` and `shallowReactive`
* keep these objects as they are. A reducer replaces `core` for each change
* and never changes it in place, thus shallow tracking is sufficient.
*/
data() {
const dataToUse = this.data;
const dataToUse = toRaw(this.data);
const schemaToUse: JsonSchema =
this.schema ?? Generate.jsonSchema(dataToUse);
const uischemaToUse = this.uischema ?? generateUISchema(schemaToUse);
toRaw(this.schema) ?? Generate.jsonSchema(dataToUse);
const uischemaToUse = toRaw(this.uischema) ?? generateUISchema(schemaToUse);
const initCore = (): JsonFormsCore => {
const initialCore = {
data: dataToUse,
Expand All @@ -135,18 +156,19 @@ export default defineComponent({
initialCore,
Actions.init(dataToUse, schemaToUse, uischemaToUse, {
validationMode: this.validationMode,
ajv: this.ajv,
// Ajv cannot compile a schema through a proxy of itself.
ajv: toRaw(this.ajv),
additionalErrors: this.additionalErrors,
}),
coreReducer
);
return core;
};
return {
schemaToUse,
dataToUse,
uischemaToUse,
jsonforms: {
schemaToUse: shallowRef(schemaToUse),
dataToUse: shallowRef(dataToUse),
uischemaToUse: shallowRef(uischemaToUse),
jsonforms: shallowReactive({
core: initCore(),
config: configReducer(undefined, Actions.setConfig(this.config)),
i18n: i18nReducer(
Expand All @@ -161,7 +183,7 @@ export default defineComponent({
cells: this.cells,
uischemas: this.uischemas,
readonly: this.readonly,
},
}),
};
Comment thread
KiaraGrouwstra marked this conversation as resolved.
},
computed: {
Expand All @@ -184,17 +206,20 @@ export default defineComponent({
},
watch: {
schema(newSchema) {
this.schemaToUse = newSchema ?? Generate.jsonSchema(this.dataToUse);
this.schemaToUse =
toRaw(newSchema) ?? Generate.jsonSchema(this.dataToUse);
if (!this.uischema) {
this.uischemaToUse = generateUISchema(this.schemaToUse);
}
},
uischema(newUischema) {
this.uischemaToUse = newUischema ?? generateUISchema(this.schemaToUse);
this.uischemaToUse =
toRaw(newUischema) ?? generateUISchema(this.schemaToUse);
},
data(newData) {
const isSameAsCurrentData = newData === this.jsonforms.core.data;
this.dataToUse = newData;
const isSameAsCurrentData =
toRaw(newData) === toRaw(this.jsonforms.core.data);
this.dataToUse = toRaw(newData);

if (
(this.schema === undefined || this.schema === null) &&
Expand Down Expand Up @@ -239,7 +264,8 @@ export default defineComponent({
this.uischemaToUse,
{
validationMode: this.validationMode,
ajv: this.ajv,
// Ajv cannot compile a schema through a proxy of itself.
ajv: toRaw(this.ajv),
additionalErrors: this.additionalErrors,
}
),
Expand Down
Loading
Loading