Vue Basic String Custom Renderer

So I’m starting out with Vue JSONForms and I’m trying to create a bare-bones custom text renderer. I know JSONForms has the vue-vanilla package, but I want to understand what are the basics needed for a custom renderer because later on I will need to do much more customization to each custom renderer I create. Here is what I have so far:

<template>
    <v-input />
</template>

<script lang="ts">
import { ControlElement, JsonFormsRendererRegistryEntry, rankWith, isStringControl } from '@jsonforms/core'
import { useJsonFormsControl, RendererProps } from '@jsonforms/vue'
import { defineComponent } from 'vue'

const renderersText = defineComponent({
    name: 'renderers-text',
    setup (props: RendererProps<ControlElement>) {
        return useJsonFormsControl(props)
    },
})

export default renderersText

export const entry: JsonFormsRendererRegistryEntry = {
    renderer: renderersText,
    tester: rankWith(1, isStringControl),
}
</script>

But I’m getting a r.tester is not a function error. Any idea what this means and/or what I need to fix? Thanks in advance!

Hi @adamsilva01,

The code looks fine on a first glance. As the error message is related to the tester: Can you show how you are registering this custom renderer?

Hey @sdirix ,

import RenderersText from '~/components/renderers/text/RenderersText.vue'
const renderers: any[] = [
    RenderersText,
]

Hi @adamsilva01,

The renderers are an array of { renderer, tester } pairs. As you only hand over the renderer itself instead of an object containing the renderer and tester, JSON Forms can’t find the tester.

Note also that a renderer set which ONLY consists of the RenderersText renderer will likely also fail as JSON Forms by default auto generates a VerticalLayout UI Schema element for a JSON Schema object. So you should also at least add a layout renderer. See this very related question.

So instead of the renderer you should import the entry. If you are using the types of JSON Forms it should already complain when using the wrong one.

import { entry as renderersTextEntry } from '~/components/renderers/text/RenderersText.vue';
import { JsonFormsRendererRegistryEntry } from '@jsonforms/core';

const renderers: JsonFormsRendererRegistryEntry[] = [
  renderersTextEntry,
  // add your layout renderer entry here
]

In case you are struggling to export the entry from the .vue file: You need to modify the TS module declaration for Vue to declare that entry is also exported. Alternatively you could just define the entry outside of the .vue SFC.

Hey @sdirix ,

Thanks for the detailed answer!
I added the vue-vanilla package which contains some layouts, and I’ve modified the code to:

import { vanillaRenderers } from '@jsonforms/vue-vanilla'
import { entry as renderersTextEntry } from '~/components/renderers/text/RenderersText.vue'
const renderers: any[] = [
    renderersTextEntry,
    ...vanillaRenderers,
]

However, now I’m getting the following error: Cannot read properties of undefined (reading 'rule')

Can you show how you invoke JSON Forms and what exactly the content of the handed over arguments is?

Unrelated to the error: Your tester needs to return a higher priority so it can reliably win against the vanillaRenderer

Hey @sdirix ,

Sure, here is the full component meant to display the form:

<template>
    <h1>JSON Forms Vue 3</h1>
    <div class="myform">
        <json-forms
            :data="data"
            :renderers="renderers"
            :schema="schema"
            :uischema="uischema"
            @change="onChange" />
    </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { JsonForms, JsonFormsChangeEvent } from '@jsonforms/vue'
import { vanillaRenderers } from '@jsonforms/vue-vanilla'
import { entry as renderersTextEntry } from '~/components/renderers/text/RenderersText.vue'
const renderers: any[] = [
    renderersTextEntry,
    ...vanillaRenderers,
]

const schema = {
    properties: {
        name: {
            type: 'string',
            minLength: 1,
            description: "The task's name",
        },
        description: {
            title: 'Long Description',
            type: 'string',
        },
    },
}
const uischema = {
    type: 'HorizontalLayout',
    elements: [
        {
            type: 'VerticalLayout',
            elements: [
                {
                    type: 'Control',
                    scope: '#/properties/name',
                },
                {
                    type: 'Control',
                    scope: '#/properties/description',
                    options: {
                        multi: true,
                    },
                },
            ],
        },
    ],
}
export default defineComponent({
    name: 'app',
    components: { JsonForms },
    data () {
        return {
            // freeze renderers for performance gains
            renderers: Object.freeze(renderers),
            data: {
                name: 'Send email to Adrian',
                description: 'Confirm if you have passed the subject\nHereby ...',
            },
            schema,
            uischema,
        }
    },
    methods: {
        onChange (event: JsonFormsChangeEvent) {
            this.data = event.data
        },
    },
})
</script>

Hey @sdirix ,

Eventually the issue was solved since I added the following to the component:

props: {
    ...rendererProps<ControlElement>(),
},

However, now I’m getting a warning from the console with the following:

[Vue warn]: Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with markRaw or usi
ng shallowRef instead of ref.
Component that was made reactive:  {
  name: 'render-text',
  props: {
    schema: { required: true, type: [Array] },
    uischema: { required: true, type: [Function: Object] },
    path: { required: true, type: [Function: String] },
    enabled: { required: false, type: [Function: Boolean], default: undefined },
    renderers: { required: false, type: [Function: Array], default: undefined },
    cells: { required: false, type: [Function: Array], default: undefined },
    config: { required: false, type: [Function: Object], default: undefined }
  },
  setup: [Function (anonymous)],
  ssrRender: [Function: _sfc_ssrRender],
}

Any idea on how to fix this? Should I manually declare the props that I need instead of using rendererProps?

I fixed the issue by adding markRaw to the renderers array, since this will make it so the components are not reactive and avoid getting the Proxy wrapped around them

Hi @adamsilva01,

Great that you found a solution!

Eventually the issue was solved since I added the following to the component […]

Ah I see the error was a Typescript problem! I thought this is about a runtime problem…

I fixed the issue by adding markRaw to the renderers array, since this will make it so the components are not reactive and avoid getting the Proxy wrapped around them

In our seed apps and package readmes we use Object.freeze.