Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better handling of form submission errors #4351

Closed
archfz opened this issue Jan 24, 2020 · 9 comments
Closed

Better handling of form submission errors #4351

archfz opened this issue Jan 24, 2020 · 9 comments

Comments

@archfz
Copy link

archfz commented Jan 24, 2020

Is your feature request related to a problem? Please describe.
The documentation is missing a section on how to display validation errors for fields that are coming from API 400 responses. Example response:

{
  "errors": {
    "myField": ["error1", "error2"]
  }
}

Describe the solution you'd like
Clear documentation with straightforward way of displaying validation errors from API.

Describe alternatives you've considered
I have found several issue and answers for this problem when react-admin still used redux-form. I've tried those solutions to no avail just to realize that it is now using react-final-form. Looking through the code I haven't found any easy or straightforward solution.

One solution could be composing the save callback provided to the create and edit view by the useEditController and useCreateController and then saving the error somehow in state and returning it in the validate callback. But this solution is in no way simple and straightforwards.

Another solution that I've implemented is using saga to capture the FETCH_ERROR and saving the error in redux. This on the other hand made a big mess on managing state as it had to be removed after displaying it, otherwise it would have affected other forms as well.

Other than that it was hard to figure out how to actually make these errors go away after displaying them, because if they were not removed you couldn't submit the form. For this I had to also save the form state values to figure out which fields changed and which error I didn't have to display anymore.

And to put the cherry on the cake making both frontend and API errors display at the same time was again quite hard. In the end I ended up with a hook of 100 lines, and a lot more lines extract from it to different services.

export default function useEntityFormValidator(
  schema: any,
  customErrorMessages?: ISchemaErrorToTranslationCustomMapper,
): (entity: any) => IValidationErrorMapUntranslated | void {
  const [
    schemaErrorTransformer,
    apiErrorTransformer,
    jointErrorTransformer,
    schemaValidator,
  ] = useDependencyInjections(
    ServiceTypes.TRANSFORMER_SCHEMA_ERROR_TO_TRANSLATION,
    ServiceTypes.TRANSFORMER_API_ERROR_TO_TRANSLATION,
    ServiceTypes.TRANSFORMER_TRANSLATION_ERRORS_TO_JOINT,
    ServiceTypes.VALIDATOR_SCHEMA,
  );
  const dispatch = useDispatch();

  const lastBadRequest = useSelector<any, IHttpError | null>(
    (state: any) => state.apiError?.lastBadRequest,
  );
  const [
    apiErrorMessages,
    setApiErrorMessages,
  ] = useState<IValidationErrorMapTranslated | null>(null);
  const [apiErrorBody, setApiErrorBody] = useState<{ [k in string]: any }>({});

  const computeRemainingApiErrorMessages = useCallback(
    (entity: any) => {
      const leftApiErrors: any = {};
      Object.keys(apiErrorMessages || {}).forEach(field => {
        if (entity[field] === apiErrorBody[field]) {
          leftApiErrors[field] = (apiErrorMessages || {})[field];
        }
      });
      return leftApiErrors;
    },
    [apiErrorMessages, apiErrorBody],
  );

  useEffect(() => {
    if (lastBadRequest?.body?.errors) {
      setApiErrorBody(lastBadRequest.requestBody);
      setApiErrorMessages(apiErrorTransformer.transform(lastBadRequest.body));

      // In order to not leave errors behind we consume it from the state.
      // Otherwise other forms could potentially display errors from wrong
      // places.
      dispatch({ type: apiErrorActions.SET_400, data: null });

      // Have to focus the body because if an input remains focused error
      // messages will not be displayed until it loses focus.
      document.body.focus();
    }
  }, [lastBadRequest]);

  return useCallback(
    (entity: any) => {
      const schemaErrors = schemaValidator.validate(entity, schema) || {};
      return jointErrorTransformer.transform(
        [
          computeRemainingApiErrorMessages(entity),
          schemaErrorTransformer.transform(schemaErrors, customErrorMessages),
        ],
        '• ',
      );
    },
    [schema, customErrorMessages, computeRemainingApiErrorMessages],
  );
}
@fzaninotto
Copy link
Member

Submission errors are indeed not well covered by react-admin, and require a lot of boilerplate code. It's not just a matter of documentation - we need better APIs on how to customize the behavior in case of submission error.

Marking this as enhancement request, feel free to suggest changes.

As a side note, here is the relevant section of the react-final-form documentation : https://final-form.org/docs/react-final-form/examples/submission-errors

@fzaninotto fzaninotto changed the title Documentation on how to display API validation errors for fields Better handling of form submission errors Jan 24, 2020
@macklin-10x
Copy link
Contributor

This is the issue preventing us from upgrading to ra 3. It seems like there's some fundamental tension in the sense that react-final-form needs errors to come out of onSubmit but react-admin wants to somehow inject them back as part of a side effect saga. redux-form provided the stopSubmit function that bridged the gap, but it seems like with react-final-form there's no way to access the form context from outside of the form.

Is there a sense of how it might be possible to extend react-admin to support this very common use case? We could potentially contribute some development time to a solution if the RA authors have ideas about how to proceed.

@sebashwa
Copy link
Contributor

FYI I shared a possible workaround to this in #871, but it is by no means more elegant or straightforward than the hook you provided @archfz

@bmihelac
Copy link
Contributor

bmihelac commented May 20, 2020

Hello everyone,

I would like to share my thoughts and get info if displaying submission errors is achievable with current react-admin:

React Final Form submission errors are stored in meta.submitError - react-admin fields could be easily updated to display this errors, ie: bmihelac@21e88e6

Final form Submission Error example expect submission errors to be returned as object literal in onSubmit. If I understand correctly, react-admin updates/creates resources as side effect and react-final-form does not recommend to mutate form state using dispatching actions:

If you need to mutate your data via dispatching Redux actions, you should probably use Redux Form.

In #4703 @oleg-andreyev propose returning promise in custom save property. I liked and have tried this in combination with useDataProvider hook (which returns promise as opposed to useMutation). Code is available in commit bmihelac@1a9f02b and it looks like this could work. Unfortunately, I am not sure if comment #4703 (comment) relates to useMutation only or that it would generally not possible to go this way, because it would break other things.

@drj17
Copy link

drj17 commented May 22, 2020

@bmihelac your submitError solution is a really good quick fix, that, at least in my use case, solves the biggest roadblock I have with react-admin right now.

@bmihelac
Copy link
Contributor

@drj17 thanks. I wish that sustainable solution for handling submission form errors is added to react-admin.

@bojangles-m
Copy link

Hello everyone, is it possible to do minor changes on the code that was proposed by @bmihelac as long as you don't find better solution.

  • to add submitError to input fields, because React Final Form submission errors are stored in this variables submitError
  • and to return the onSave function like on this link
    What do you think?

@alanpoulain
Copy link
Contributor

Hello 🙂
I've made a PR to handle properly the submission validation errors: #5778.

@fzaninotto
Copy link
Member

With the addition of server response validation in 3.12, we can now close this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants