AJV only validate on input

Hey,

As soon as my JSON forms loads up and the renderers get rendered, all of the fields get validated by AJV and they all turn red with the error messages. Is there a way to not trigger the validation on page load?

EDIT:
I found this in the coreReducer code:

case INIT: {
      const thisAjv = getOrCreateAjv(state, action);

      const validationMode = getValidationMode(state, action);
      const v = validationMode === 'NoValidation' ? undefined : thisAjv.compile(action.schema);
      const e = validate(v, action.data);

From the last line, it seems it will always validate the form when JSON Forms boots up. Is there any workaround to this?

Hi @adamsilva01,

You might want to try our validation modes. You can find the documentation for them here.

Hey @sdirix ,

But none of those modes validates only on input.
ValidateAndShow validates as soons as the form loads and shows all the errors
ValidateAndHide does the same except it will always hide the errors even after input
NoValidation doesn’t even validate.

Should I use ValidateAndHide when the form loads, and then change it to ValidateAndShow?

EDIT: This doesn’t work, as soon as the value changes to ValidateAndShow, the errors get shown again.

Basically I only want each input to validate itself when it’s changed and show an error if invalid, I don’t want the full page to show errors as soon as the form loads.

I guess what you’re asking for is showing errors subsequently this might has to do with ajv.

i dont recollect much but i had this issue where the errors where coming subsequently and i had to edit the property of ajv

ajv = createAjv({
allErrors: false
})

you can try setting allError to false with all other Error properties you have.

hope this helps.

Hi @adamsilva01,

We currently don’t offer “validate on input” out of the box. To achieve something like this you will need to write custom renderers (which can mostly just wrap our existing renderers), which track an additional “touched” state and only show their errors when touched

This is not really a useful solution. Yes only a subset of errors will be shown, but AJV can’t know which fields were touched and which weren’t and therefore the shown error will very likely not match the user input flow.

1 Like

Thanks for mentioning ive got a question how can you make an input entere element as invalid with jsonform.updateerrors

for example we can make it as min or max is there anyway we can make it as invalid like incase of api? also can show an example for jsonform.updateErrors for min or max in angular

Hi @howdyAnkit,

I would like to suggest to use the additionalErrors prop instead of dispatching an updateErrors action. You can find the documentation here.

Hey @sdirix ,

Nice! I managed to implement it quite easily since most of my renderers are already custom.
I’m just having an issue with my ObjectRenderer. In this case, it only displays/computes the errors when something is inserted. Meaning, before any letter is inserted in one of the fields, none of the fields get evaluated for errors (only the parent property does).

Not sure if there is anything I need to change in my ObjectRenderer? Now I’d rather this show all the errors even for ObjectRenderers, since I can now control them via the new touched property I created.

Here is my Object Renderer:

<template>
    <div v-if="control.visible">
        <dispatch-renderer :visible="control.visible"
                           :enabled="control.enabled"
                           :schema="control.schema"
                           :uischema="detailUiSchema"
                           :path="control.path"
                           :renderers="control.renderers"
                           :cells="control.cells" />
    </div>
</template>

<script lang="ts">
import {
    ControlElement,
    findUISchema,
    Generate,
    isObjectControl,
    JsonFormsRendererRegistryEntry,
    rankWith,
    UISchemaElement,
} from '@jsonforms/core'
import { DispatchRenderer, rendererProps, RendererProps, useJsonFormsControlWithDetail } from '@jsonforms/vue'
import { defineComponent } from 'vue'
import { useVanillaControl } from '@jsonforms/vue-vanilla'

const applyFormRenderObject = defineComponent({
    name: 'object-renderer',
    components: { DispatchRenderer },
    props: {
        ...rendererProps<ControlElement>(),
    },
    setup (props: RendererProps<ControlElement>) {
        const control = useVanillaControl(useJsonFormsControlWithDetail(props))
        return {
            ...control,
            input: control,
        }
    },
    computed: {
        detailUiSchema (): UISchemaElement {
            const uiSchemaGenerator = () => {
                return Generate.uiSchema(this.control.schema, 'VerticalLayout')
            }

            return findUISchema(
                this.control.uischemas,
                this.control.schema,
                this.control.uischema.scope,
                this.control.path,
                uiSchemaGenerator,
                this.control.uischema,
                this.control.rootSchema,
            )
        },
    },
})

export default applyFormRenderObject

export const entry: JsonFormsRendererRegistryEntry = {
    renderer: applyFormRenderObject,
    tester: rankWith(2, isObjectControl),
}
</script>

Hi @adamsilva01,

Does the object actually exist before any field is touched? AJV will not produce errors for properties for an object which is undefined. Once you edit a field JSON Forms will by default create the parent object if it does not exist which then let’s AJV report errors for all its properties.

To fix that you need to make sure that the object already exists in your data, either by initializing manually or using AJV’s default option.

Hey @sdirix ,

The data object is empty at the beginning. I tried using the both true and 'empty' for the useDefaults options, but none of those worked. I even tried creating the empty object on the data, but still didn’t work.

Here is the schema for the object being rendered using the ObjectRenderer I sent above:

"/personal/name": {
      "title": "Name",
      "description": "First and last name",
      "type": "object",
      "properties": {
        "first": {
          "type": "string",
          "minLength": 2,
          "maxLength": 30
        },
        "last": {
          "type": "string",
          "minLength": 2,
          "maxLength": 30
        }
      },
      "required": [
        "first",
        "last"
      ]
    },

Hey @sdirix ,

What’s the best/simplest way to get the validationMode inside the renderers?
The only way I can find is to inject the jsonforms into each of the renderers and then access the validation mode through jsonforms.core.validationMode.

Is there a better way?

Thanks in advance!

Hi @adamsilva01,

Conceptually that’s the intended way. However if you do that often then I would “hide” that behind a HOC to avoid code duplication and not break React.memo barriers, similar to the withAjvProps HOC.

Hey @sdirix ,

I have a custom layout that basically replaces the native layout and wraps each custom renderer.

Would a better solution then be to inject jsonforms into the custom layout and pass the validationMode to the dispatch renderer as prop so each of the custom renderers has access to it via the props?

That would avoid the code duplication, but not sure if it would still “break React.memo barriers” since I’m not familiar with that.

I think the best solution is to write and use the HOC as discussed above. You can then wrap all your custom controls which need access to the validationMode with the HOC and thereby you make it available as a prop.

1 Like

Thank you for the help!

One more thing, regarding this issue that was discussed some time agora in this thread about errors in the ObjectRenderer:

Does the object actually exist before any field is touched? AJV will not produce errors for properties for an object which is undefined. Once you edit a field JSON Forms will by default create the parent object if it does not exist which then let’s AJV report errors for all its properties.
To fix that you need to make sure that the object already exists in your data, either by initializing manually or using AJV’s default option .

Is there a way to get around this without initialising manually or using the defaults? I’m currently using the ObjectRenderer from the vue-vanilla package, but if I can solve this issue by create a custom ObjectRenderer then I’d probably prefer that if possible.

Thanks in advance!

Hallo @sdirix ,

thank you for the discussion. I got so far to having a working “touched” reactive property in my custom renderer. How do I go hiding the error messages if touched is “false”.
rawErrors = computed(() => input.control.value.errors);
so I can’t set that to empty.
Doing this.control.errors = ""; unfortunately does not obviously clear the errors, or maybe it does but
just after that ajv validator gets called, and then the errors appear.

When is a good moment to set the errors to empty in the renderer: onChange? onGetValue

Thank you for your help!

Hi @viktor-c,

In your renderer you can create a new reactive errors value, e.g.

const possiblyFilteredErrors = computed(() => touched ? input.control.value.errors : '');

And then you use that one instead of errors in your renderers.

Thank you @sdirix That was the missing link :slight_smile:
By using the computed value, it automatically gets updated when touched gets updated. I was trying with watch