Monika
Monika

Reputation: 87

React: How to change form validation from onSubmit validation for all fields, to onBlur instant validation for each field in the form?

I have the form which is functioning great, but I need to make the validation instant (as soon as the user defocuses the field, I want the error message shown for that field only). At the moment I have a validation that shows error messages when the submit button is clicked:

enter image description here

Here is the code:

  1. The useForm hook
import { useState, useEffect } from 'react';

const useForm = (callback, validate, post) => {
    const [values, setValues] = useState(post || {});
  const [errors, setErrors] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  useEffect(() => {
    if (Object.keys(errors).length === 0 && isSubmitting) {
      callback();
    }
  }, [errors]);

  const handleSubmit = (event) => {
        if (event) event.preventDefault();
        console.log('values in useForm', values)
        setErrors(validate(values))
    setIsSubmitting(true);
  };

    const handleInputChange = (event) => {
        event.persist();
    setValues(values => ({ ...values, [event.target.name]: event.target.value }));
    };
    


  return {
    handleInputChange,
        handleSubmit,
    values,
    errors,
  }
};

export default useForm;

  1. The validate function
const validate = (values) => {
    const errors = {};
    
    if (!values.title) {
        errors.title = 'Title is required'
    } else if (values.title.length < 5) {
    errors.title = 'Title must be at least 5 characters long'
    }

    if (!values.body) {
        errors.body = "Blog body is required"
    } else if (values.body.length < 2 || values.body.length > 20) {
        errors.body = "Text has to be between 2 and 20 characters long"
    }

    if (!values.author) {
    errors.author = "The author's name is required"
    }
    
    if (!values.number) {
        errors.number = "A number is required"
    }
    
 if (!values.email) {
         errors.email = 'Email is required';
 } else if (
         !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
 ) {
         errors.email = 'Invalid email address';
 }

 return errors;
}
export default validate;
  1. The form component
import { useHistory } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import  useForm from '../hooks/useForm';
import { addPost } from '../actions';
import validate from '../helpers/validate';


const CreateForm = () => {
    const dispatch = useDispatch();
    const history = useHistory();
    const { values, handleInputChange, handleSubmit, errors } = useForm(submit, validate)

    const { title, body, author, number, email } = values;
    
    function submit() {
        console.log("No errors", values)
        const post = values;
      console.log('Submit form', post)
      dispatch(addPost(post)) 
      history.push('/');
    }
    

    return ( 
        <div className="create">
            <h2>Add a new blog</h2>
            <form onSubmit={handleSubmit} noValidate>
                <label>Blog title:</label>
                <input
                    type="text"
                    required 
                    name="title"
                    value={title || ""}
                    onChange={handleInputChange}
                    className={errors.title ? 'red-border' : ""}
                />
              {errors.title && (<p className="danger">{errors.title}</p>)}

                <label>Blog body:</label>
                <textarea
                    required
                    name="body"
                    value={body || ""}
                    onChange={handleInputChange}
                    className={errors.body ? 'red-border' : ""}
                />
                {errors.body && (
          <p className="danger">{errors.body}</p>
        )}
                <label>Author:</label>
                <input
                    type="text"
                    required
                    name="author"
                    value={author || ""}
                    onChange={handleInputChange}
                    className={errors.author ? 'red-border' : ""}
                />
                {errors.author && (
          <p className="danger">{errors.author}</p>
        )}
                <label>Number:</label>
                <input
                    type="number"
                    required
                    name="number"
                    value={number || ""}
                    onChange={handleInputChange}
                    className={errors.number ? 'red-border' : ""}
                />
                  {errors.number && (
          <p className="danger">{errors.number}</p>
        )}
                <label>Email:</label>
                <input
                    type="text"
                    required
                    name="email"
                    value={email || ""}
                    onChange={handleInputChange}
                    className={errors.email ? 'red-border' : ""}
                />
                  {errors.email && (
          <p className="danger">{errors.email}</p>
        )}
                <button>Save</button>
            </form>
        </div>
    );
}
 
export default CreateForm;

Upvotes: 1

Views: 8697

Answers (2)

Slim
Slim

Reputation: 683

As dicussed in the comments, you just need to call your setError when you want to update your error helpers. Here's a live example that flags an error if you type "error" in any of the fields: https://codesandbox.io/s/wispy-monad-3t8us?file=/src/App.js

const validateInputs = (e) => {
    console.log("validating inputs");
    if (e.target.value === "error")
      setErrors({ ...errors, [e.target.name]: true });
    else setErrors({ ...errors, [e.target.name]: false });
  };

        <input
          type="text"
          required
          name="title"
          value={values.title}
          onChange={handleInputChange}
          style={errors.title ? { border: "2px solid red" } : null}
          onBlur={validateInputs}
        />

Upvotes: 3

Arya
Arya

Reputation: 124

As you said in your question, you should call validate() in onBlur instead of in onSubmit.

So just add onBlur event in each of your input:

           <input
                type="number"
                required
                name="number"
                onBlur={validate}
                onChange={handleInputChange}
            />

Upvotes: 0

Related Questions