0

Problem I'm trying to solve: Form validation for an app built in React that takes in user input and generates a CV. This is for learning purposes and part of The Odin Project curriculum.

How I'm trying to solve form validation: I have my project organized with a large formData object set as state in App.js and I'm sharing that state to all child components via useContext. For validation, I have given each piece of CV data 2 properties in my formData object. Example below:

{firstname: '', firstNameValid: true}

I am trying to write a function (see attached code) that sets the validity of each propertyValid and set it to false.

What I expect: When a field is invalid (like firstName), it sets firstNameValid: false when I run the invalidateField('firstName') function.

What happens instead: Logging the formData reveals that firstNameValid is still set to true.

What I have tried: As seen in the attatched code, I am trying to make a copy of my formData object, set only the value I want, and then just manually set it using setFormData(formCopy). However, when I log them together, I see that while formCopy looks like what I want it to be, the formData in state still has not changed. I am updating state just fine from my grandchildren components, but I'm unable to do it here and I don't understand why.

//my object declared in state
  const [formData, setFormData] = React.useState({
    firstName: '',
    lastName: '',
    email: '',
    firstNameValid: true,
    lastNameValid: true,
    emailValid: true
    //a lot more than this but you get the idea
  });

//function in question that is not working
  function invalidateField(string){
    //takes the name of the key as a string
    let key = `${string}Valid`;
    let value = false;
    let formCopy = {...formData};
    formCopy[key] = value;
    
    setFormData(formCopy);
    console.log(formCopy, formData);
  }

//function I'm writing to check validity of input fields
//called by button click elsewhere in code
  function formIsValid(formData){
    let validity = true;
    if(formData.firstName.length < 1){     //firstName = '' (field is empty)
      console.log('your first name is too short');
      invalidateField('firstName');
      validity = false;
    }
    return validity;
  }


//context then passed to child components in the return statement.
manski
  • 131
  • 1
  • 10
  • Does this answer your question? [The useState set method is not reflecting a change immediately](https://stackoverflow.com/questions/54069253/the-usestate-set-method-is-not-reflecting-a-change-immediately) – Konrad Jan 19 '23 at 17:57
  • I saw this post when first researching my question, and it didn't really help. I am updating state with very similar methods in my child components, and the changes are always reflected immediately. I'm very new to React so I don't fully understand what they mean by closures, but I don't think it applies to this situation. – manski Jan 19 '23 at 18:02
  • 1
    Using `console.log` after updating the state will still show the old value. If you want to see the new value add the `console.log` in the body of the component – Konrad Jan 19 '23 at 18:09

1 Answers1

3

The setFormData method is an async function. It takes a while until the state is updated. You console.log right after calling setFormData, thats why it looks as if your setState didnt work properly when it just needs a little more time to complete.

Above your invalidateField function you could write an useEffect to print out when your state has changed:

 import { useEffect } from "react";
    
    useEffect(() => {
      console.log(formData);
    }, [formData]);

This will execute the console.log as soon as formData has changed.

  • Thanks I will try this and see if it works. I didn't realize that ALL setState functions were async. – manski Jan 19 '23 at 18:03
  • 1
    Okay yes thank you. That useEffect worked as intended and turns out my code was functioning exactly as intended the whole time. But thank you, this was a valuable lesson in how setState functions work. – manski Jan 19 '23 at 18:10