简体   繁体   中英

Child Stateless Functional Component doesn't update even after parent State updates

My parent component has a property in its state called formIsValid , initially set to false. My form also has a submit button. I want the submit button to be disabled until after some input fields (a first name and a last name input) have data in them.

This is what my state looks like:

  state = {
    employees: [],
    costEstimates: emptyCosts(),
    relationshipOptions: [],
    newEmployee: emptyEmployee(),
    formIsValid: false
  };

This function handles changes to the First and Last name inputs:

  // handle input into "First Name" and "Last Name" inputs
  handleChangeValue = async e => {
    const newEmployee = { ...this.state.newEmployee };
    newEmployee[e.currentTarget.name] = e.currentTarget.value;

    this.setState({ newEmployee });

    this.validateIfCanBeSubmitted();

    await this.updateCostsData(newEmployee); // this is an api thing, not relevent
  };

This is what sets the formIsValid property in the state. This property is sent as a prop to the Submit button.

  validateIfCanBeSubmitted = () => {
    const { firstName, lastName } = this.state.newEmployee;

    let formIsValid = firstName && lastName ? true : false;

    this.setState({ formIsValid });
  };

The Submit button for this is correctly getting disabled if the employee property in the state has its first and last names as empty. The problem is that it's "off by 1 update." It's as if the props aren't getting propagated down to the child button component until after the NEXT time the state changes. Here's a gif of the issue:

在此处输入图片说明

This is what the child component looks like. It's just a regular HTML button, however it's within a Stateless Functional Component, so the issue is not with the component's state:

    <button
      type="button"
      onClick={onSubmit}
      className={'btn btn-primary mr-1 ' + (formIsValid ? '' : 'disabled')}
      disabled={!formIsValid}
    >

setState() is asynchronous!
this.validateIfCanBeSubmitted(); is executed on the old state; this update this.setState({ newEmployee }); has not been propagated to this.state when your function is executed.

Make validateIfCanBeSubmitted an update-function.

validateIfCanBeSubmitted = ({ newEmployee: { firstName, lastName }}) => {
  return {
    formIsValid: firstName && lastName ? true : false
  };
}

and use it accordingly:

handleChangeValue = async e => {
  const {name, value} = e.currentTarget;
  const newEmployee = { 
    ...this.state.newEmployee, 
    [name]: value
  };

  this.setState({ newEmployee });
  this.setState(this.validateIfCanBeSubmitted);

  // this is an api thing, not relevant
  await this.updateCostsData(newEmployee); 
};

Actually, the code in handleChangeValue should also be in such a function, as it uses the previous state to compute the new one.

so how about combining them:

handleChangeValue = e => {
  const {name, value} = e.currentTarget;
  this.setState((state) => {
    const newEmployee = { 
      ...this.state.newEmployee, 
      [name]: value
    };
    const { firstName, lastName } = newEmployee;
    const formIsValid = firstName && lastName ? true : false;

    //and since you never use the returned Promise, why make anything async?
    this.updateCostsData(newEmployee);

    return { newEmployee, formIsValid  };
  });
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM