React-jsonschema-form: Allow the insertion of errors from outside JSONSchema validation

Created on 21 Apr 2016  Â·  30Comments  Â·  Source: rjsf-team/react-jsonschema-form

It's great to be able to define a regex to validate an input, however, that regex should really not be shown to the user. I'd like to be able to define an error string "Your username must only contain letters, numbers and underscores" rather than having it display the regex it isn't matching... no average user can understand what it's looking for from displaying a regex.

Most helpful comment

As an alternative solution, can there be an approach similar to what redux-form does with SubmissionError https://redux-form.com/7.1.1/docs/api/submissionerror.md/

So if onSubmit is a function that returns a Promise, then it can throw SubmissionError, that will be translated into errorSchema and global errors accordingly. That way users won't need to manage additional properties for the form and all they would need to do is translate server errors in appropriate format.

I can do the PR for that.

All 30 comments

Oops. errorSchema appears to cover this.

Nope, I don't think it does. I thought that was a way to provide error messages.

You're right, we must find a way to override default error messages. Unfortunately, there doesn't seem to be any documented way to achieve just that in jsonschema, which is what we use for JSONSchema validation :/

The same way this component has the uiSchema, it'd be great to provide a way to define error messages where it's something outside of the scope of jsonschema. I've had to just set the .panel.errors to display:none because of all of the "instance" stuff that is not end-user-ready and that it is not using the "title" for a field. Something like "instance.first_name" is not ok to display to a user. So my solution is I just let it put the errors under the fields and hide the error list and avoid regex validation.

I should also be able to include my own error messages. For example, I created component that wraps this one that is designed to work with standard REST endpoints and Rails error messages. I'd love to be able to tell the component that "email" is already in use and have that error message appear under the email field just as it would for jsonschema validation errors. The error back from Rails has the field name that (in this common situation) matches the schema field name.

I've seen other comments about wanting to be able to perform validations yourself, that are outside of what jsonschema validations supports (passwords must match, etc). I'd like to see an a prop that allows you to provide an externally derived errorSchema that gets blended with what the component came up with on it's own. Devs could either have it touch the server and generate an errorSchema to display or just have additional client-side validations that get mixed in... this is probably a separate issue. But this would let me work around the issues I am having.

We definitely want to add support for custom validation with associated relevant custom error messages. "Blending" these things with what our jsonschema validation lib exposes as data and API can get tricky though.

Todo:

  • Accept an errorSchema prop to merge with the form internal one so you can inform it some fields are in an error state according to some external validation rule; the issue is how could we possibly know when such fields are not invalid anymore?
  • Accept some validationSchema object to define external validation rules for fields (as suggested in #145);

I have a feeling the right way of solving all this would be to extract the whole validation system from the component internals, so users would be able to augment it with their own rules, messages and so on.

Something like this (dumb code):

import Form, { validateJsonSchema, mergeErrorSchema } from "react-jsonschema-form";

const schema = {
  type: "object",
  properties: {
    pass1: {type: "string"},
    pass2: {type: "string"}
  }
};

const validate = ({formData}) => {
  const errorSchema = validateJsonSchema(schema, formData);
  const {pass1, pass2} = formData;
  if (pass1 !== pass2) {
    return mergeErrorSchema(errorSchema, {
      pass2: {
        errors: ["Passwords don't match"]
      }
    });
  }
  return errorSchema;
};

<Form schema={schema} validate={validate} />

Also if we go that route we probably need to allow performing async validation, so we need to support returning a Promise.

Edit: I realize this doesn't even address how we override the jsonschema default error messages. Duh.

Edit: I realize this doesn't even address how we override the jsonschema default error messages. Duh.

It seems we actually have access to all the arguments we need to expose an api to define custom error messages from the error objects jsonschema returns: https://github.com/tdegrunt/jsonschema/issues/147#issuecomment-154168376

Now back to square one for this very issue: what kind of API should we expose to leverage custom jsonschema error messages?

So, the types of validations I would need (and think this covers all):
1) jsonschema validations. (ex: MinLength, required fields, etc.) Prevents submission.
2) Client-side synchronous validations. (ex: Passwords don't match, etc.) Prevents submission.
3) Server-side/client-side async validations. (ex: Username is already taken, etc.) Pre-submission, prevents submission.
4) Server-side rejections. (ex: Rails 422 unprocessable entity error... any types of errors that prevented saving the record.) Happens _after_ data submission, no changes made to DB, errors need to display inline and identically to jsonschema errors.
~5) Overriding the default messages. "instance.password_confirmation" is not something that is presentable to a user. The regex are much worse.

Currently this component only supports type 1.

Granted, this component's scope doesn't cover posting to the server, so it just needs to have the right events and props to allow a user to do that part on their own.

So, one option would be to have a canSubmit/isValid prop event, when if present is evaluated either as a boolean or a Promise for a boolean. Then have a prop for external errors that can be set by any part of that process (or a different process entirely).

I don't like the proposal that the API having an onValidate event that expects a return of the error schema because it limits when I can set it. Just let me generate my list however and whenever I want. Many times this will be when the event fires to validate, but possibly also after I submit to the server and get error messages back. Using the pattern I described above (with pseudocode below) would cover the types 1-4 listed above, and provide me a workaround for #5 (by just keeping some validations out of jsonschema and do them client side with my own error messages).

const schema = {...}
...
  validateEvent({formData}) {
    return fetch(...call to server to check username availability)
       .then(...set the state to update external errors, returns FALSE if there are errors. but throwing an error may be much better than just returning false, as it's much more deliberate)
  }
  onSubmit(...) {
     fetch(...do post to endpoint to save)
       .catch(...set the state to show server errors; this.setState({externalErrors}))
       .then(...move to next page, give message that data posted, etc)
  }
  render() {
   const {externalErrors} = this.state
   return  <Form schema={schema}  ....
       onValidate={this.validateEvent.bind(this)} 
       onSubmit={this.onSubmit.bind(this)}
       externalErrors={externalErrors}  />
  }

I'm thinking that part of the Error Schema should be whether the error should prevent submission. Errors that don't have any client-side catches or async server checks would need to allow the form to post since it will be during the post that it gets rejected and the errors returned. Passwords not matching would be something that prevents form submission, however, but a spam filter that only evaluates during form submission would not prevent submission since it is only evaluated during the submit process. The above pattern would support all of the types mentioned.

However, this does raise problems for when the pre-submit validations should occur. Checking username availability you'd want to have some delay and not check for every single keystroke... so things can get more complex given certain situations.

Is it possible to disable validation now?

It's possible to disable live validation entirely but not validation on submission.

I can do PR.

Prop noValidate override liveValidate and disable validation onSubmit. Are there any pitfalls?

Would be great to have a PR, thanks! A noValidate attribute sounds good. Should be trivial to implement (famous last words).

Is there any work being done for this? Is it really so that there is currently no possible way to show errors that comes from a backend? So if the server responds 400 for a form requests, we just need to handle it some other way?

This seem like a gaping hole in the functionality of this project if the above statement is correct. Handling errors that comes from the backend would be great since then the backend can take care of translations and message text while the front-end (react) can keep to the view/rendering part.

But I will be happily proven wrong in that react-jsonschema-form does not support external errors to be injected in the form.

I've had to develop my own somewhat hacky way to get server errors to
display inline. There are unconventional ways to do it but I agree that
it's a huge product gap to not support it.

On Mon, Feb 20, 2017 at 10:57 PM Frank Wickström notifications@github.com
wrote:

Is there any work being done for this? Is it really so that there is
currently no possible way to show errors that comes from a backend? So if
the server responds 400 for a form requests, we just need to handle it some
other way?

This seem like a gaping hole in the functionality of this project if the
above statement is correct. Handling errors that comes from the backend
would be great since then the backend can take care of translations and
message text while the front-end (react) can keep to the view/rendering
part.

But I will be happily proven wrong in that react-jsonschema-form does not
support external errors to be injected in the form.

—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
https://github.com/mozilla-services/react-jsonschema-form/issues/155#issuecomment-281260799,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABGSlhmlFWWZ1T29dWGdOYwJV28_xFD8ks5reorTgaJpZM4IMgq7
.

Do you have any examples of the how you are doing it right now? If not it is ok, just wondering if there would be even some way of inserting error messages without needing to fork the project and create some ugly hack. If I had the time I would try to do a proper fix, but right this moment I don't have that luxury.

I have a wrapper component that receives the errors from my rails-based API
server on submit and formats the error object the way the form wants it and
I set the form component's internal state iirc. That's a bad pattern to use
but it works for me until this project can round out support for passing in
external errors thru props.

I can give more code when I'm not writing this on my phone if needed. I
chose to not fork.

I understand that the project is a generic form and not made to be aware of
back ends at all but even just giving users an "externalErrors" prop that
we could pass in form submission errors or errors that took an external
call to validate (username already in use) it would be very helpful.

Paul

On Mon, Feb 20, 2017 at 11:22 PM Frank Wickström notifications@github.com
wrote:

Do you have any examples of the how you are doing it right now? If not it
is ok, just wondering if there would be even some way of inserting error
messages without needing to fork the project and create some ugly hack. If
I had the time I would try to do a proper fix, but right this moment I
don't have that luxury.

—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
https://github.com/mozilla-services/react-jsonschema-form/issues/155#issuecomment-281264566,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABGSlnlWz1hLGqCk53UeqcIeNruBR0Adks5repCrgaJpZM4IMgq7
.

Alright, thanks for the information. I might try that, but as you say it is a bit hacky :) Hopefully full support for external errors will be introduced at some point as this project looks really nice otherwise and uses a proper standard for structuring the JSON, but the lack of external errors is currently keeping me back from using it.

@frwickst No, as far as I know nobody is working on this. It hasn't been relevant to our use case. Patches welcome :)

@glasserc : How do you handle events where the server does _not_ return a successful response? Say that a server responds with an error message saying that some part of the form is not correct? Do you do create a global toast/notification in those cases or do you have any other solutions?

Would async validation solve your issue? It's been stalled forever in #198, so anybody wanting to take over it is more than welcome.

@frwickst The projects where we use react-jsonschema-form just use the same JSON schema to validate on the server side as well as on the browser, so we never have this kind of error message. Any other error messages (for instance, server not available) aren't specific to the form and don't need to be mingled with the errors of the form. That's why I said this kind of error isn't really relevant to our use case. For us, react-jsonschema-form is really just a JSONSchema form, although I understand that many people are using it as a generic form construction library.

@nuclearspike Will appreciate if you can share some of your code on how you handle server errors.

@galiai

.catch(({ response, json }) => {
        if (response) {
          switch (response.status) {
            case 422:
              const toDisplay = json.errors ? json.errors : json;
              //errors is output in an alert in a bullet list above the form.
              const errors = Object.keys(toDisplay).map(key => `${prettifyCamelCase(key)} ${toDisplay[key].join(', ')}`);

              this.setState({errors});
              break;
           [....other handlers]
            default:
              this.setState({errorMessage: `An unhandled error has occurred. ${response.status}: ${response.statusText}`});
          }
        }

I used to have it set the state of the schema form component but that trick stopped working at some point. Now I just output the 422 errors above the form.

One of the techniques I'm currently using is to have a ref on the submit button to manually invoke the form validation (it's documented on the README's Tips & Tricks section as external form control https://jsfiddle.net/spacebaboon/g5a1re63/). So the idea is that when this.state.errors (errors from the server) has changed, I am triggering the form validation (by programatically clicking the button). The second half of the technique is to inject errors into the form, which I use the custom validation function passed in via the 'validate' prop. However, in order to allow the user to press submit again to make the server call, I have to prevent the merging of the server error into the custom validation (which right now, I do that by clearing this.state.errors).

function injectServerResponse(response) {
  this.setState({ serverResponse: response });
  this.submitButton.click(); // Triggers form validation
  this.setState({ serverResponse: null }); // This allows user to continue to submit
}

function customValidation(formData, errors) {
  const { serverResponse } = this.state;
  if (_.isObject(serverResponse)) {
    if (serverResponse.details) {
      errors.addError(serverResponse.details);
    }
    if (_.isObject(serverResponse.validation)) {
      _.each(serverResponse.validation.fieldValidationErrors, (error) => {
        if (errors[error.fieldName] && error.errorDescription) {
          errors[error.fieldName].addError(error.errorDescription);
        }
      });
    }
  }
  return errors;
}

I don't really like this solution because it relies on having the form state unsynchronized from the parent component's state.

I found what looks to be a better example of triggering validation from https://github.com/mozilla-services/react-jsonschema-form/issues/197#issuecomment-284147942 by extending the Form class and calling this.validate directly.

The whole point of creating a json schema is to be able to define a generic form.
Relating to content of the schema from the code should be enabled as a last resort, but IMO, there should be a more generic approach to this problem - meaning that the error strings should be defined in the json file itself, rather than a function in the code (which couples the code with the schema)

As an alternative solution, can there be an approach similar to what redux-form does with SubmissionError https://redux-form.com/7.1.1/docs/api/submissionerror.md/

So if onSubmit is a function that returns a Promise, then it can throw SubmissionError, that will be translated into errorSchema and global errors accordingly. That way users won't need to manage additional properties for the form and all they would need to do is translate server errors in appropriate format.

I can do the PR for that.

uhh.. I think it would be best if the Form component had a errorSchema prop. The error list would be generated from the schema and the changes (like all state) would come through the onChange and onSubmit callbacks. This would allow you to control the errorSchema like you can the other schemas / formData.

Try out #874... still needs docs and tests.

Found this issue too late. Hope to see #874 merged soon!
Meanwhile I moved a promise based approach to a small package: https://github.com/bopen/react-jsonschema-form-async
It works for my usecase.

Continue discussion on this wrapper issue: #1389

Was this page helpful?
0 / 5 - 0 ratings