The "pattern" attribute on the text field was really handy for
triggering HTML5 validation that the input matched a regular
expression. The only real downside is that it gives a truly generic
error message:
Please match the requested format.
That doesn't actually tell the user waht to fix, (in this case, to add
a number). I really wish there existed another attribute to simply
provide the error message that should be provided. If there is, I
couldn't find one.
I did have the title set here, which is close, but with two problems:
1. The resulting error message isn't _just_ my text, it is:
Please match the requested format:
<My title text here>
so I don't quite get as much control as I want.
2. The title text shows up immediately when hovering over the input
field, (even before the user has ever typed anything). But this
is an error message that I only want the user to see if they've
committed an error.
It took me quite a lot of struggling to come up with a solution for
this minor issue that works the way I want. Here it is:
1. I remove the pattern attribute, (so I don't get any default
validation of the input format). This is essential or else the
"Please match the requested format" text will appear at least
somewhere.
2. In the subit handler, I validate the input against my regular
expression and call setCustomValidity on the input field to set
the text I want to appear.
3. But _also_ immediately after doing this I _also_ call
reportVisibility on the form element. Otherwise, the input field
will still get styled as invalid as I want, but the error message
won't actually get reported, (unless the user tries submitting
_again_ while the input is still invalid).
4. Next I also have to arrange to _clear_ this invalid state. So for
this I add a new onChange handler (so it will be called for every
keystroke). For this, a lot of tutorials just call
setCustomValidity with an empty string here unconditionally. The
downside of that is that the field will get styled as valid as
soon as the user makes any change. Instead, I check the regular
expression here so that the field is only styled as valid once
the mistake is corrected. (This is consistent with the behavior
of the default HTML5 validation with the "pattern" attribute.)
So in the end, this gives the behavior that I want. It's a little
wordy, especially here in my explanation!, but also in the code:
Particularly that two different handlers are required: One to set the
error state and one to clear it. As I implemented things here the
regular expression is even duplicated in those two cases, (but that's
a defect that could be addressed---the pattern could be stashed in a
common place if I cared to do it).
Note that it would be possible to set the error state in an 'else'
clause within my onChange handler, (and that would eliminate the
duplication of the regular expression pattern). The reason I don't do
that here is that it would cause the field to be styled with the
angry-red "invalid" styling as soon as the user started typing, rather
than waiting for form submission before validation happens. So that
could be annoying to users (it would drive me crazy) and it would also
be inconsistent with how HTML5 validation happens with the "pattern"
attribute.
super(props);
this.category = React.createRef();
+ this.handle_change = this.handle_change.bind(this);
this.handle_submit = this.handle_submit.bind(this);
}
+ handle_change(event) {
+ const category_input = this.category.current;
+ const category = category_input.value;
+
+ if (/[0-9]/.test(category))
+ category_input.setCustomValidity("");
+ }
+
handle_submit(event) {
const category_input = this.category.current;
const category = category_input.value;
/* Prevent the default page-changing form-submission behavior. */
event.preventDefault();
+ if (! /[0-9]/.test(category)) {
+ category_input.setCustomValidity("Category must include a number");
+ event.currentTarget.reportValidity();
+ return;
+ }
+
console.log("Do something here with category: " + category);
}
type="text"
id="category"
placeholder="6 things at the beach"
- required pattern=".*[0-9]+.*"
- title="Category must contain a number"
+ required
+ onChange={this.handle_change}
ref={this.category}
/>
</div>