skip to Main Content

I have a React app with a form containing multiple input fields and checkboxes. If fields are blank or unchecked I have individual functions that check each of those respective fields to determine if they’re blank or unchecked before proceeding with an API call to submit the data.

I call those check functions when the form button is clicked. If the check passes the API call is made, if it fails I’d like to display a list of errors that the user can correct. The button is clicked again, the errors are cleared and rechecked, etc.

Where I’m struggling is with maintaining the state of an array where I keep the list of errors. The errors are inserted into the array on each function call and then mapped and listed out.

Problem: Only the last error is showing in the array instead of all of them. The clear function isn’t clearing the array. On the first click of the submit button, the error is appearing as intended, but the console.log(errorArray) is showing an empty array.

export default function ProfileForm(props) {

  const [formErrors, setFormErrors] = useState([]);

  const hasLinkRequest = () => {
    if(linkRequest !== ""){
      return true
    } else {
      const addLinkError = [...formErrors, "Missing Link Request"];
      setFormErrors(addLinkError);
      return true;
    }
  };

  const hasStringFields = () => {
    if (
        pocFirstName !== "" &&
        pocLastName !== "" &&
        email !== "" &&
        phoneNumber !== "" &&
        streetAddress1 !== "" &&
        city !== "" &&
        state !== "" &&
        zipcode !== "" &&
        zipcode.length == 5
      ){
      return true
    } else {
      const addFieldError = [...formErrors, "Missing Required Field"];
      setFormErrors(addFieldError);
      return true
    }
  };


  const hasCraftsmanship = () => {
    var crafts = [];
    Object.values(craftsmanships)
    .sort(sortCraftsmanshipFn)
    .map(({id, name, has}) => {
      if(has == true) {
        crafts.push(crafts);
      }
    });
    if(crafts.length > 0){
      return true
    } else {
      const addSkillError = [...formErrors, "Missing Service Types"];
      setFormErrors(addSkillError);
      return true
    }
  };

  const clearErrors = () => {
    console.log(formErrors);
    const emptyArray = [];
    setFormErrors(emptyArray);
    console.log(formErrors);
  }

  const canSubmit = () => {
    clearErrors();
    hasStringFields();
    hasLinkRequest();
    hasCraftsmanship();
    if(formErrors.length > 0) {
      return true
    } else {
      return false 
    }
  };

const onFormSubmit = (e) => {
    e.preventDefault();
    if (canSubmit()) {
   run API call
 } else {
    "we display the list of errors"
 }
}

<FORM CONTENT>
    <ErrorAlert errors={formErrors} />
    <Button onClick={onFormSubmit} />
</END OF FORM>


}

function ErrorAlert(props) {
  const { errors = {} } = props;

  return (
    <div className='div-block-34'>
      {Object.keys(errors || {}).map((key) => (
        <div className='text-block-12' key={key}>{`${errors[key]}`}</div>
      ))}
    </div>
  );
}

When I click the button, I’d like the array to store all of the applicable error messages. Right now, it only displays "Missing Service Types" the first time I click the button. Each subsequent click keeps adding more "Missing Service Types" to the formErrors array. If I update the Service Type field, then re-click it, it shows the "Missing Link Request" error along with the previous error. So it’s only updating the array with the last function in the canSubmit function.

I’m not showing the full form and fields as the form works fine, it’s just this error handling.

2

Answers


  1. To avoid concurrency affectation, you’ve to take the old state, not the current.

    For exemple:

      const addSkillError = "Missing Service Types";
      setFormErrors(oldErrors => [...oldErrors, addSkillError]);
    

    Instead of :

      const addSkillError = [...formErrors, "Missing Service Types"];
      setFormErrors(addSkillError);
    
    Login or Signup to reply.
  2. You have to use callback function with useState.

    // hasLinkRequest
    setFormErrors(curr => ([...curr, "Missing Link Request"]));
    
    // hasStringFields
    setFormErrors(curr => ([...curr, "Missing Required Field"]));
    
    // hasCraftsmanship
    setFormErrors(curr => ([...curr, "Missing Service Types"]));
    
    

    It is not working because you are using the formErrors variable directly, and adding new error to it. It holds the value from the state in the last render. Hence when you call all four function in sequence synchronously, and then you are creating a new array out of this array (without mutating the same array). It always update the state with previous state of formErrors.

    This is what happen on clicking submit button

    On First Submit

    const addLinkError = [...formErrors, "Missing Link Request"];
    // this creates new array from formErrors without updating formErrors
    // addLinkError -> ["Missing Link Request"]
    // formErrors -> []
    
    const addFieldError = [...formErrors, "Missing Required Field"];
    // this creates new array from formErrors without updating formErrors
    // addLinkError -> ["Missing Required Field"]
    // formErrors -> []
    
    const addSkillError = [...formErrors, "Missing Service Types"];
    // this creates new array from formErrors without updating Array formErrors
    // addLinkError -> ["Missing Service Types"]
    // formErrors -> []
    
    

    Hence after running all three functions it is set to ["Missing Service Types"]

    On 2nd Submit

    const addLinkError = [...formErrors, "Missing Link Request"];
    // this creates new array from formErrors without updating formErrors
    // addLinkError -> ["Missing Service Types", "Missing Link Request"]
    // formErrors -> ["Missing Service Types"]
    
    const addFieldError = [...formErrors, "Missing Required Field"];
    // this creates new array from formErrors without updating formErrors
    // addLinkError -> ["Missing Service Types", "Missing Required Field"]
    // formErrors -> ["Missing Service Types"]
    
    const addSkillError = [...formErrors, "Missing Service Types"];
    // this creates new array from formErrors without updating Array formErrors
    // addLinkError -> ["Missing Service Types", "Missing Service Types"]
    // formErrors -> ["Missing Service Types"]
    
    

    Hence after running all 3 function it is set to ["Missing Service Types", "Missing Service Types"]

    To understand this better I am adding another way of doing it (Not recommended)

    // Create a new array
    let newErrorArray = []
    // then in respective function you do add errors
    
    // hasLinkRequest
    newErrorArray.push("Missing Link Request")
    setFormErrors(newErrorArray)
    
    // hasStringFields
    newErrorArray.push("Missing Required Field")
    setFormErrors(newErrorArray)
    
    // hasCraftsmanship
    newErrorArray.push("Missing Service Types")
    setFormErrors(newErrorArray)
    

    This will work since you are updating the same variable which was is going to be used in next function. Hence it will hold the value from previous function.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search