0

I'm trying to add a new subobject to an existing object using the method this.setState in a expo application. The subobject are added after a click of a button, that update the fields that compons the subobject.

This is my code:

import React, { Component } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, ImageBackground } from 'react-native';

import CenteredButton from '../components/CenteredButton';
import { Actions } from 'react-native-router-flux';

var t = require('tcomb-form-native');
var _ = require('lodash');

const Form = t.form.Form;
const stylesheet = _.cloneDeep(t.form.Form.stylesheet);

stylesheet.textbox.normal.borderColor = '#b3b3b5';
stylesheet.textbox.normal.fontFamily = 'RobotoThin';
stylesheet.textbox.normal.backgroundColor = '#fdfdfd';
stylesheet.textbox.normal.fontSize = 18;
stylesheet.textbox.normal.borderWidth = 0.6;
stylesheet.textbox.normal.borderRadius = 10;

stylesheet.textbox.error.fontFamily = 'RobotoThin';
stylesheet.textbox.error.backgroundColor = '#fdfdfd';
stylesheet.textbox.error.fontSize = 18;
stylesheet.textbox.error.borderWidth = 0.6;
stylesheet.textbox.error.borderRadius = 10;

const Email = t.refinement(t.String, email => {
  const regex = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/; 
  return regex.test(email);
});

const EmailTo = t.struct({
  emailPerson: Email,
  emailInsurance:  t.maybe(Email)
});

const options = {
  auto: 'none',
  stylesheet: stylesheet,
  fields: {
    emailPerson: {
      placeholder: 'Email personale',
      autoCapitalize: 'none',
      autoCorrect: false,
    },
    emailInsurance: {
      placeholder: 'Email Assicurazione',
      autoCapitalize: 'none',
      password: true,
    }
  }
}

export default class NessunProblema extends Component {

  constructor(props) {
    super(props);
    this.state = {
      emails: {
        emailPerson: '',
        emailInsurance: ''
      },
      ascertainment: { }
    }
  }

  componentDidMount() {
    this.setState({ ascertainment: this.props.ascertainment });
  }

  _onChange = (emails) => {
    this.setState({ emails });
  }

  _handle = () => {
    const value = this.refs.form.getValue();
    if ( value ) {
      this.setState(prev => ({
        ascertainment: {
          ...prev.ascertainment,
          emails: {
            ...prev.ascertainment.emails,
            emailPerson: value.emailPerson,
            emailInsurance: value.emailInsurance
          }
        }
      }));
    }

    console.log(this.state.emails);
    console.log(this.state.ascertainment);
  }

  render() {
    return (
      <View style={{flex: 1, backgroundColor: 'white' }}>
        <ImageBackground source={require('../images/NoProblem.png')} style={styles.backgroundImage}>
          <View style={{ flex: 2, alignItems: 'center', justifyContent: 'center', width: '100%', paddingHorizontal: 20, top: 10}}>
            <Text style={styles.domanda}>
              Text
            </Text>
            <Text style={styles.domanda2}>
              Text
            </Text>
          </View>

          <View style={{padding: 20}}>
            <Form 
              ref='form'
              options={options}
              type={EmailTo}
              value={this.state.emails}
              onChange={this._onChange}
            />
          </View>

          <CenteredButton
            next={ this._handle }
          />
        </ImageBackground>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  domanda: {
    color: '#00b0ff',
    textAlign: 'center',
    fontSize: 44,
    fontFamily: 'RobotoRegular',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 20
  },
  domanda2: {
    color: 'black',
    textAlign: 'center',
    fontSize: 22,
    fontFamily: 'RobotoRegular',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 20
  },
  testoRosso: {
    color: '#f32a19',
    fontFamily: 'RobotoRegular',
  },
  backgroundImage: {
    flex: 1,
    resizeMode: 'cover'
  },
  textInput: {
    width: '100%',
    paddingHorizontal: 15,
    height: 40,
    marginBottom: 20,
    fontSize: 18,
    borderWidth: 0.6,
    borderColor: 'black',
    borderRadius: 10,
    color: 'black',
    fontFamily: 'RobotoThin',
    backgroundColor: 'white'
  },
});

I noticed that, if I click TWO times the button AVANTI I obtein the correct result. But, WHY?

I follow this answer but doens't resolve the problem.

th3g3ntl3man
  • 1,926
  • 5
  • 29
  • 50

1 Answers1

1

The problem is that setState() is not synchronous, the values are updated asynchronously

setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value. There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.

_handle = () => {
    const value = this.refs.form.getValue();
    if ( value ) {
      this.setState(prev => ({
        ascertainment: {
          ...prev.ascertainment,
          emails: {
            ...prev.ascertainment.emails,
            emailPerson: value.emailPerson,
            emailInsurance: value.emailInsurance
          }
        }
      }));
    }

    console.log(this.state.emails); // will be the emails in the previous state, since setState has not been called yet by react 
    console.log(this.state.ascertainment); // if you click twice, you are still getting the `last` state, but since it is the same as the state you are setting the second time, you get the wrong idea that it is being set if you click twice!
  }
Dhananjai Pai
  • 5,914
  • 1
  • 10
  • 25