Client Side Form Validation in React


Updated Apr 19th, 2022

Immediately versus After Delay

Not so much a React thing but very important. Some warnings need to be shown right away, (too many characters in a field). Others need to be shown after a delay, (when the user has finished typing a username check and see if it already exists or make sure there is a minimum number of characters).

There are other client side warnings that are checked on submit, like if the field is left blank, (more on this below).


It seems wordy or verbose but I think this may have to do with how surprisingly complicated client side validation for a form with many inputs can be.

onBlur versus onChange versus onSubmit

When leveraging useReducer, the onChange is absolutely necessary for updating state with every keystroke for immediate checks like too many characters.

When it comes to checking if a field is blank you can use an onBlur but you end up with an undesired result. When the page loads there is no error but if you changes pages the error may flash briefly. Also, if you click into the field and then click out, whether you every type into the field or not the error will show. Alternatively you may want to have the “field cannot be left empty warnings onSubmit only.

How Do You Clear the Fields?

Using a case like “clearFields” and setting “draft.whateverField.value to an empty string does not change the front end. So how do we update the field?

You should have two way binding on the form input, meaning there is a “value” prop set to the state and an “onChange” that updates the state.

            onChange={e => {
              dispatch({ type: "incorrectAnswer3Immediately", value: })

Avoid One of My Pet Peeves

Something is wrong with the form so it clears the form requiring you to start over. You don’t see this much anymore, thank goodness, but you’ve likely experience it before.

Warning Times Out

If you set a timer to let the warning timeout you end up with more code because you need to make those checks again when the form is submitted. I still like this though. You can accomplish this in a “setTimeout” inside a “useEffect,” don’t forget the teardown function. Not sure if it is better to have multiple “useEffect” hooks, or multiple items in the dependency array.

  useEffect(() => {
    if (state.description.hasErrors || state.price.hasErrors || state.miles.hasErrors) {
      const timer = setTimeout(() => {
        dispatch({ type: "removeAnyErrors" })
      }, 3000)
      return () => {
  }, [state.description.hasErrors, state.price.hasErrors, state.miles.hasErrors])


Auto complete can be annoying so you may want to set “autoComplete” to off on the input itself.

Speaking of “autoComplete,” don’t forget “autoFocus.” This is not so much a validation thing but good form UX.

textarea has little “expand” handle in the bottom right of the textarea by default. This can be removed but it’s done in the CSS.

textarea has a “rows” option but it’s value is not in quotes but in curly brackets.

Don’t forget your “name” and “aria-label” attributes on the inputs and the “htmlFor” on the labels.

Likely want to trim values when checking if a field is left blank to avoid spaces from clearing your check.