Custom field validation with errors outside of JSONForms and AJV

Hi,

I’d like to run some custom validation logic for a field in my JSONForms form. If this logic finds a problem, it should display an error on the field in exactly the same way as the standard AJV errors are displayed. However, the custom validation logic should not have any dependency on AJV (since we don’t want our JSON Schema to become non-standard), and should support both synchronous and async execution.

The posts I’ve found advise against using the updateErrors action directly, but that seems like the most appropriate solution based on what I can tell so far.

Can you provide an example of how to properly access that action, and the dispatch method needed for it, from a React form in a Typescript project? I’m not quite sure what contexts, hooks, etc are needed, and where they should go. Additionally, what is the shape of the input that updateErrors expects?

Many thanks!
Bryce

Hi @BryceLohr, currently you can’t easily add errors from outside of JSON Forms. To nicely support this the following changes would need to be made to JSON Forms: Accept an errors parameter in the root component, storing this in an additional field in the overall state and then mixing it in whenever the internal validation runs. I would like to have this at some point in JSON Forms but we didn’t get to it yet.

Within JSON Forms you could use the setErrors action to update the whole errors array. The problem is that whenever the internal validation runs the errors are overwritten again. So you would need to run the setErrors action for every data change. This is definitely possible but also not ideal. It would also mean you have an additional render pass and maybe a bit of flickering in the affected controls.

A cleaner alternative is to use custom renderers. They could just reuse the existing renderers but you can then mix in your additional errors at this point. No additional render pass is then needed. However if your custom validation can affect potentially all controls then this would mean that you need to reregister all existing control renderers.

Can you provide an example of how to properly access that action, and the dispatch method needed for it, from a React form in a Typescript project? I’m not quite sure what contexts, hooks, etc are needed, and where they should go. Additionally, what is the shape of the input that updateErrors expects?

Within a custom renderer you can execute the following:

const {core, dispatch} = useJsonForms();
const currentErrors = core.errors;
dispatch(Actions.updateErrors([...currentErrors, ...myAdditionalErrors]);

dispatching will trigger a new render pass, so you should not do it unconditionally on each render pass.

Additionally, what is the shape of the input that updateErrors expects?

It expects AJV.ErrorObjects. Note that we just updated AJV from version 6 to 8 but did not do a release with it yet. That’s important as the shape of the error object (slightly) changed between versions.

Hi Stefan,

Thank you so much for the detailed reply! For context, we are planning to build a control library of custom renderers so that we can achieve our desired look and feel for the entire form. So it sounds like it isn’t too hard to inject our custom errors within each control with the useJsonForms hook.

I see what you’re saying about state overwrite problem with setErrors, and thanks for flagging that. Just curious, what exactly is setErrors, and where would one call it from? Is a dispatcher action separate from Actions.updateErrors, but obtained from the same place? This is just so I can understand more clearly; it seems actually using it is problematic.

Lastly, given that we will have a custom control library, if we wanted to have a separate validation process that runs on the client when the form is submitted (before posting to the server) and produces field-specific error messages, what would be the best way to display those messages in the form? It sounds like we’d actually need to expose our own dispatcher from the custom components that the form-wide validation could use to send error messages to fields. Is there another approach I’m overlooking?

Thanks again for the help, and for the great work on the library!

I see what you’re saying about state overwrite problem with setErrors , and thanks for flagging that. Just curious, what exactly is setErrors , and where would one call it from? Is a dispatcher action separate from Actions.updateErrors , but obtained from the same place? This is just so I can understand more clearly; it seems actually using it is problematic.

Sorry, my mistake. I meant updateErrors but wrote setErrors. There is no setErrors action.

Thank you so much for the detailed reply! For context, we are planning to build a control library of custom renderers so that we can achieve our desired look and feel for the entire form. So it sounds like it isn’t too hard to inject our custom errors within each control with the useJsonForms hook.

Perfect, as you basically have your own renderer set the proposed approach makes a lot more sense

Lastly, given that we will have a custom control library, if we wanted to have a separate validation process that runs on the client when the form is submitted (before posting to the server) and produces field-specific error messages, what would be the best way to display those messages in the form? It sounds like we’d actually need to expose our own dispatcher from the custom components that the form-wide validation could use to send error messages to fields. Is there another approach I’m overlooking?

One possible way is to put an additional binding between the ones of JSON Forms and your own, for example

const MyRenderer = withJsonFormsControlProps(withMyCustomErrorBinding(MyRendererComponent));

Your custom withMyCustomErrorBinding receives all props from withJsonFormsControlProps and just passes them through to MyRendererComponent. The only difference is that you can check your own error validation and modify the errors prop accordingly. Then your errors (and if you want to also the errors from JSON Forms) are displayed in your fields.

For this you should probably create an own React context and manage your errors there. The context can be consumed in your bindings.

Note that this is just one approach. As you’re writing custom controls anyway you can also handle it differently of course, for example directly in your renderers.

1 Like

Thank you so much, Stefan! The approach of another HOC to act as “error middleware” seems pretty solid to me, and I think we’ll try doing that for our error display logic. Very helpful!