Christoph Hummler
Christoph Hummler

Reputation: 1081

How to make sure the setState calls are already executed after accessing this.state?

I have a registration form. When I click the Register button all TextInputs are validated and for each field I call a function in which I call e.g. this.setState({usernameError: 'This field is required'}). So for each text field I have a function to set the error state for this field. After I press the Register button I want to validate all fields and then return the function if there is any field error in the state (The user may not register if all fields have not been filled in correctly). The problem is: Because setState is asynchronous, it is not guaranteed that all field errors are already correctly set when I check them.

2 important annotations:

  1. I know the second parameter (the callback) of setState, but I can't execute the condition (which checks the field errors in the state) in this callback because the setState calls are located in their own function
  2. They are located in their own function because I want to reuse the logic (I also call the validate<FieldName> functions in other places, e.g. when the onChangeText event occurs)

So my question is: How to make sure the state is already updated when checking its values without giving up my reusable "validator functions"?

Some code to make it clearer:

state = { usernameError: '', firstNameError: '', lastNameError: '' };

setUsernameError = () => { // This function is also called elsewhere than in handlePressRegister
    const { username } = this.state;
    if (username === '')
        this.setState({ usernameError: 'Required field' });
    else if (username.length < 2)
        this.setState({ usernameError: 'Minimum 2 characters' });
    else
        this.setState({ usernameError: undefined });
}

setFirstNameError = () => { // This function is also called elsewhere than in handlePressRegister
    ...shortened for brevity...
}

setLastNameError = () => { // This function is also called elsewhere than in handlePressRegister
    ...shortened for brevity...
}

handlePressRegister = () => {
    const { usernameError, firstNameError, lastNameError } = this.state;

    this.setUsernameError();
    this.setFirstNameError();
    this.setLastNameError();

    if (usernameError || firstNameError || lastNameError) // The errors may not be set in the state yet
        return;

I hope you understand my question correctly.

Upvotes: 1

Views: 274

Answers (2)

Nicholas Tower
Nicholas Tower

Reputation: 85132

I would handle this by having the various set[X]Error functions return their new value. For example:

setUsernameError = () => { 
    const { username } = this.state;
    let error = undefined;
    if (username === '')
      error = 'Required field';
    else if (username.length < 2)
      error = 'Minimum 2 characters';

    this.setState({ usernameError: error });

    return error;
}

handlePressRegister = () => {
  const usernameError = this.setUsernameError();
  const firstNameError = this.setFirstNameError();
  const lastNameError = this.setLastNameError();

  if (usernameError || firstNameError || lastNameError) {
    return;
  }
  // ...
}

Upvotes: 1

CertainPerformance
CertainPerformance

Reputation: 371049

If you promisify setState, you could return it from each validator call and call Promise.all on it:

setUsernameError = () => { // This function is also called elsewhere than in handlePressRegister
    const { username } = this.state;
    if (username === '')
        return setStatePromisified({ usernameError: 'Required field' });
    // ...
Promise.all([
    this.setUsernameError(),
    this.setFirstNameError(),
    this.setLastNameError(),
])
    .then(() => {
        // state will be updated now
        const { usernameError, firstNameError, lastNameError } = this.state;
        if (usernameError || firstNameError || lastNameError) {
            return;
        }
        // submit?
    });

Another option would be to add another property into state, say, validating, that gets set when the register button is pressed. The next render, detect its change in a componentDidUpdate, reset validating, and continue with the logic as needed (submit the form if no errors?).

Upvotes: 1

Related Questions