react icon indicating copy to clipboard operation
react copied to clipboard

[BUG] isValid flag on submission is set wrongly when number field is added to the form

Open mm-dsibinski opened this issue 3 years ago • 2 comments

Environment

Please provide as many details as you can:

  • Hosting type
    • [] Form.io
    • [x] Local deployment
      • Version: N/A
  • Formio.js version: 4.13.9
  • Frontend framework: React
  • Browser: Google Chrome
  • Browser version: Version 95.0.4638.69 (Official Build) (64-bit)

Steps to Reproduce

  1. Download the sample create-react-app project with reproducible example: formio-validation-number.zip

  2. In the app main catalog, execute npm install and then npm start to start the web app.

  3. On the web app, you can see a form. By default, it renders a form (both forms' JSONs are hard-coded in formView.jsx file) with one text area and one checkbox. You can see that the text area control is required:

image

The text area has no value filled, so the whole form is invalid and shouldn't be submitted (it contains validation errors).

  1. Open dev tools (F12) and see that when clicking on "Form without number" button (which simply re-renders this form), the submission parameter of onChange function has isValid flag correctly set to false:

image

This is so far correct.

  1. Now click on "Form with number" button. This button does nothing more than changing the form prop of the Form component to the one with one additional control - a field of type number. When you click this button once, see what happens in the devtools console (F12) with isValid flag:

image

This is wrong. The isValid flag on submission object should still be false. Validation of the form is still not passing (form is still in an invalid state).

Expected behavior

When changing the form on Form component, the isValid flag on submission object passed in onChange callback function should not change. It should always reflect the actual validation status of the form.

Observed behavior

submission.isValid flag changes from false to true when only form definition is changed (number control is added). I also noticed that it doesn't happen with all controls. If you add another text area, this problem doesn't occur.

More info

It might be related to #411 It's very important to fix that. It makes it impossible to block saving/submitting of the form when it has some validation errors, and you are using a custom submit button (not the FormIO's default one defined in form JSON as a component). It's not reliable at all and users of our application are getting frustrated with these strange validation issues.

mm-dsibinski avatar Nov 10 '21 08:11 mm-dsibinski

I'm having the same sort of issue with the isValid flag. For me, the isValid flag is reporting that the form is valid on initial render, but the Form's internal state seems to contradict that state since the included Submit button is disabled still. Without access to the form's ref (#407), this makes using a custom submit button almost impossible with this library since I can't manually call the checkValidity function either.

@mm-dsibinski Did you manage to find any workaround to this isValid issue in the meantime?

Thanks!

crcollver avatar Jun 10 '22 23:06 crcollver

@crcollver unfortunately no... Validation in FormIO component is totally unreliable. We've already done many "haxes" in the JSONs of the forms/submission objects for different things, because FormIO is veeery buggy, but haven't found any solution for validation issues.

mm-dsibinski avatar Jun 13 '22 06:06 mm-dsibinski

For me I just went ahead and coded my own check.

// data
const { o_form, o_submission } = props;  // feed your own stuff
const [construct] = React.useState(  o_form  ); // the formio json definition from database / etc.
const [originalSubmission] = React.useState( {data:   o_submission  } ); // initial form data to load.

const [canSubmit, setCanSubmit] = React.useState(true);
const [formReady, setFormReady] = React.useState(false);
const [submission, setSubmission] = React.useState({}); // value fed back from FormIO upon change.

// reactive events
const onChange = (submission) => {
	setSubmission(submission);
};
const onFormReady = (instance) => {
	setFormReady(true);
};

React.useEffect(() => {
	if (formReady) {
		let valid = submission.isValid;
		if (valid) { // isValid is seemingly flawed @ initialization: https://github.com/formio/react/issues/415
			valid = checkValidity(submission);
		}
		setCanSubmit(valid);
	}
}, [submission]);

// if any required field is blank, obviously the form isn't really valid. Supplemental fix for an issue #415.
const checkValidity = (data, _default) => {
	let valid = _default;
	for (let comp of construct.components) {
		if (comp.validate && comp.validate.required) {
			var checkForAnythingAtAll = data[comp.key];
			if (!checkForAnythingAtAll || checkForAnythingAtAll === '') {
				valid = false;
				break;
			}
		}
	}
	return valid;
};

// display
return (
<React.Fragment>
	<Form 
		form={construct} 
		submission={originalSubmission}
		formReady={onFormReady}
		onChange={onChange}
	/>
	<Button disabled={ !canSubmit }>
		Weee!
	</Button>
</React.Fragment>
);

React renders the form, then it triggers formReady, then it triggers onChange with initial empty(or default or provisioned) values for the form. So it's 3 render cycles. 1st cycle: isValid=false. Same for 2nd. 3rd cycle, isValid=true. 1st cycle is obviously the initial render. 2nd is caused by the form ready listener. 3rd is some sort of internal onChange callback occurring, perhaps due to the 2nd render cycle serving up props to the <Form> (even though they are unchanged from 1st).

In the OP's post above, perhaps you do not have 3 render cycles. The workaround still applies. Just explaining the internal flow here.

So idea in above is simply to override the isValid boolean with our own logic for the "initial" (3rd) render cycle. You could supplement your own logic for checkValidity, if you don't have any required fields..

After that, the onChange event properly sends us isValid without any custom code needed (it starts working again).

Without a ref, perhaps we need some utility functions (execute: validate(data) ) exposed via using hooks to comply with react. something like...

import {Form, useFormIO} from '@formio/react';
const {utility} = useFormIO();

.... code from above ...
valid = utility.Validate(submission);

Now that would be a proper workaround until a deep dive is possible by author and staff. Mine above is janky but it does the job for me.

Easier said than done I know. Just a suggestion. If I had time I'd dig into it and help, but i have none. I shouldn't even be here right now typing this.

ensemblebd avatar Jan 03 '23 02:01 ensemblebd

We're currently addressing a backlog of GitHub issues. Closing this thread as it is outdated. Please re-open if it is still relevant. Thank you for your contribution!

jeriah-formio avatar Feb 14 '24 14:02 jeriah-formio