Search code examples
javascriptreactjsreact-hooksjavascript-objectsreact-functional-component

React: Cannot update object property in state in function component


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.

Solution

  • 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.