Tcomb-form-native: problem with init value and enum

Created on 5 Aug 2016  Â·  15Comments  Â·  Source: gcanti/tcomb-form-native

I

I have a form containing some fields of type enum and I want to be able to populate the form with previously stored values from my server, when they exist, and to keep the fields with default empty (-) value when not the case.
The problem is that, if I don't provide a complete initial structure to init the form, fields of type enum can't validate until I changed them to a different value.

Currently, the code I have (and that work) is this:

 constructor(props) {
    super(props);
    this.app = this.props.app;

    this.state = {
       loading: true,
       saving: false,
       initialvalue: {
          name:"",
          gender: "M",             
          birthdate: new Date(),          
          address: "",
          country: "FR",
          langs: []
       },
    }
 }

componentWillMount() {
    this.setState({loading: true});
    this.app.service('users').get(this.app.get('user')._id).then((result) => { <-- I retrieve previously stored data from my server. If they exist
        this.setState({
          initialvalue: {
              name: result["name"],
              gender: result["gender"],             
              birthdate: new Date(result["birthdate"]),          
              address: result["address"],
              country: result["country"],
              langs: result["langs"]
          }
        }); 
        this.setState({loading: false});
        console.log('value changed');
    }).catch((err) => {
        console.log('err');
        console.log(err);
        this.setState({initialvalue: {}}); 
        this.setState({loading: false});
    });


render() {
    return (
      <TouchableWithoutFeedback onPress={this._dismissKeyboard.bind(this)}>
       <ScrollView>
        <View style={baseStyles.container}>
          <Form
           ref="form"
           type={Person}
           options={options}
           value={this.state.initialvalue}
          />

The problem with this code is that, if some fields do not yet exists in my server, these fields will be populated by the default values defined in the constructor (for example, the gender would be "M").
I tried to remove the initialvalue from the constructor or to to set it as initialvalue:{} but, in that case, the form bug and refuse to validate until I manually change the value of enum fields to a value different to the one retrieved from my server.

So how can I initialize a form with some value, when they exist in my server, and keep it blank if not the case?

My conf:

  • tcomb-form-native v0.6.0
  • react-native v0.30

Most helpful comment

I think there's a validation error when changing the Select to any value. I need more time to dig deeper into the issue! Thanks for reporting @ramsestom.

All 15 comments

Can you share the form type Person? I think that you should set some values to optional using t.maybe.

No I don't get any optional field.

Here is my Person struct and my enums def:

var Form = t.form.Form;
var Gender = t.enums({
  M: 'Male',
  F: 'Female'
}, "Gender");
var tpc = {};
for (let c of countries){
  tpc[c["Code"]]=c["Name"];
}
var Country = t.enums(tpc, 'Country');
var tpl = {};
for (let l of languages){
  tpl[l["code"]]=l["name"];
}
var Lang = t.enums(tpl, 'Language');

var Person = t.struct({
  name: t.String,
  gender: Gender,             
  birthdate: t.Date,          
  address: t.String,
  country: Country,
  langs: t.list(Lang) 
});

You have to wait till' the server response to create your t.struct, because you don't have any country or language list I assume.

You could also use the technique described here (dynamic forms) to update the options of your form on-the-fly when you get the API response.

Anyway, I'm having hard time trying to understand what's exactly your problem and how it is related to the library (there's certainly no bug in the behaviour you seem to describe). It seems more a StackOverflow question, though.

Just to recap, if you define a t.struct like this

const PointStruct = t.struct({
  x: t.Number,
  y: t.Number,
})

The form will return always a Struct containing both x and y or null if it doesn't pass validation. If you need to have some fields without value, you can change to this

const PointStruct = t.struct({
  x: t.Number,
  y: t.maybe(t.Number),
})

And that could produce Struct {x: 0, y: null}. You can check which values are null before sending the data to your server.

No I have a predefined country and language list (that are stored in another js file I import)
So creating the enums is definitively not the issue here. And I am already dynamically updating my form when I receive answer from my api call.
I will try to explain differently the problem I have (not really easy to explain though).

consider only the "gender" field of my form:

  • If I do not initialize it in my component class constructor (so if I have initialvalue: null or initialvalue:{} for example) and then update it dynamically when my api call (called in the componentWillMount() function) return, the gender field in the form is correctly updated. But then, I can't validate the form until I manually change the gender type (If my api called retured "male" for example, I am forced to changed it to "female" before beeing able to validate the form)
  • if I initialize it in my component class constructor (like in the code of my first post) and update it when my api return, then I have no issue validating the form without changing its value. But in that case the problem is that, if my api call do not return any gender value (because user do not yet provide this information, so it's the first time he sees that form), then the form would be filled with the gender value provided at initialization (in the class constructor. Which is "M=male" in my case). And I don't want that, I want to keep the gender 'undefined' (appear with a '-' in the form) for new users.

You can probably reproduce the issue by having a form with only a "name"(String) and "gender"(enum) fields that you initialize or not in the constructor and update dynamically (simulate an api call with a timer in the componentWillMount() function) and you will see more concretely what I am trying to explain ;)

You have to use t.maybe in the gender if it's not a mandatory field.

And keep in mind that it's better to make API calls in componentDidMount.

gender is a mandatory field.
I think you don't get the issue here. Will prepare an example you can run to see the issue

Sorry, but it seems that you have another problem related with the source code and not with this library. It seems more a conceptual problem rather than a library bug. I'm trying to help here, keep that in mind.

You said this

if my api call do not return any gender value (because user do not yet provide this information

and

I want to keep the gender 'undefined'

So this automatically means that the field is not mandatory. If it is, it must have a value when you submit it.

If you get an API response with a gender value and it is displayed in the form, that's the expected behaviour. If you don't get the gender value in the API response but you have provided a default value, the form will display a default value, which is also the expected behaviour, and an undefined value for gender it's not going to validate the form because the field _is mandatory_.

No the gender field is mandatory.

  • if my server api call do not return any gender because this is the first time the user fill the form, I want the user to provide his gender before being able to validate the form (so the gender will appear as "undefined" when the user open the form but the user HAS TO provide his gender to be able to validate the form)
  • if my server api call return a gender because the user already filled that form before (so the information is on the server), the gender that appear when the user open the form should be the one provided by the server and the user must be able to change it or not before validating the form.

@alvaromb a field can be mandatory but also not having a _current_ value while the user interact with the form. Actually all mandatory fields have no value when you display a form for the first time and you don't provide initial values. A mandatory field must have a value though when you call the getValue method on the Form component.

the gender field in the form is correctly updated. But then, I can't validate the form until I manually change the gender type

@ramsestom Definitely that should not happen. If you call the validate() method on the Form component and dump the returned value you can inspect the current values stored in the form and the associated errors. It can be helpful to debug this issue

Will prepare an example you can run to see the issue

Yes, thanks. A minimal failing example would be great

Ok. Written communication is hard, definitely I haven't understood the problem.

So, the field is mandatory but it seems that the form state could be wrong after API response. Can you show me your form state before and after the API response? I think you may be storing weird values and that could be the root of the issue.

Enviado desde mi iPhone

El 5 ago 2016, a las 18:01, Giulio Canti [email protected] escribió:

@alvaromb a field can be mandatory but also not having a current value while the user interact with the form. Actually all mandatory fields have no value when you display a form for the first time and you don't provide initial values. A mandatory field must have a value though when you call the getValue method on the Form component.

the gender field in the form is correctly updated. But then, I can't validate the form until I manually change the gender type

@ramsestom Definitely that should not happen. If you call the validate() method on the Form component and dump the returned value you can inspect tha current values stored in the form and the associated errors. It can be helpful to debug this issue

Will prepare an example you can run to see the issue

Yes, thanks. A minimal failing case would be great

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

OK. Here is a failing example:

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  TextInput,
  Dimensions, 
  Platform,
  View,
  ScrollView,
  TouchableHighlight,
  TouchableWithoutFeedback,
  BackAndroid,
  Alert
} from 'react-native';
import t from 'tcomb-form-native';

var Form = t.form.Form;
var Gender = t.enums({
  M: 'Male',
  F: 'Female'
}, "Gender");

var Person = t.struct({
  name: t.String,
  gender: Gender             
});

var options = {};

export default class AppTest extends Component {

 constructor(props) {
    super(props);
    this.saveInfos = this.saveInfos.bind(this);
    this.state = {
       loading: true,
       saving: false,
       initialvalue: {
          name:"init name",
          gender: "M",             
       },
    }
 }

  componentDidMount() {
    this.setState({loading: true});
    setTimeout(() => {
         this.setState({
          initialvalue: {
              name: "server name",
              gender: "M"
          }
        }); 
        this.setState({loading: false});
    }, 1000);
 }

 saveInfos() {
    var self = this;
    var infos = this.refs.form.getValue();
    if (infos) 
    { 
        this.setState({saving: true});
        Alert.alert("Success","Saving on server");
    }
  }

  renderButton() {
    if (this.state.loading) {
      return (
        <View style={{alignItems: 'center'}}>
          <Text>Loading infos...</Text>
        </View>
      );
    }
    else if (this.state.saving) {
      return (
        <View style={{alignItems: 'center'}}>
          <Text>Saving infos...</Text>
        </View>
      );
    }
    else {
      return (
       <View style={{justifyContent: 'center', alignItems: 'center', height: 50, flexDirection: 'column'}}>
          <TouchableHighlight style={[styles.baseButton, styles.buttonPrimary]} onPress={this.saveInfos} underlayColor="transparent">
            <Text style={[styles.baseButtonText, styles.buttonPrimaryText]}>Save Infos</Text>
          </TouchableHighlight>
        </View>
      )
    }
  }

 render() {
    return (
      <TouchableWithoutFeedback>
       <ScrollView>
        <View style={styles.container}>
           <Form
           ref="form"
           type={Person}
           options={options}
           value={this.state.initialvalue}
          />
          {this.renderButton()}
        </View>
        </ScrollView>
      </TouchableWithoutFeedback>
    );
  }

}


const styles = StyleSheet.create({
 container: {
    justifyContent: 'center',
    marginTop: 50,
    padding: 20,
    backgroundColor: 'white',
    //flex: 1,
    //alignItems: 'center'
  },
  baseButton: {
    flex: 0.8,
    flexDirection: 'column',
    marginBottom: 5,
    width: Dimensions.get('window').width - 30,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 8
  },
  baseButtonText: {
    fontSize: 20,
    fontWeight: "600",
    fontFamily: 'HelveticaNeue-Thin'
  },
  buttonPrimary : {
    backgroundColor: '#31D8A0',
    borderColor: '#31D8A0',
    marginTop: 10
  },
  buttonPrimaryText : {
    color: 'white'
  },
  title: {
    fontSize: 30,
    alignSelf: 'center',
    marginBottom: 30
  },
  buttonText: {
    fontSize: 18,
    color: 'white',
    alignSelf: 'center'
  },
  button: {
    height: 36,
    backgroundColor: '#48BBEC',
    borderColor: '#48BBEC',
    borderWidth: 1,
    borderRadius: 8,
    marginBottom: 10,
    alignSelf: 'stretch',
    justifyContent: 'center'
  }
});

AppRegistry.registerComponent('AppTest ', () => AppTest);

This works but force the gender to "M" even if I remove the componentDidMount() function that simulate the server api call

Now, if you change initialvalue: {}orinitialvalue: null in the constructor, you will see the issue (unable to validate the form because of the gender field)

@alvaromb The api response is not the issue here because if I initialise my form with some default value before changing it with my api response, everything is fine (I am able to validate the form). This is only when I do not provide a default value before updating my form with api response that I have issue with enums fields...

Will test later today. Thanks.

I think there's a validation error when changing the Select to any value. I need more time to dig deeper into the issue! Thanks for reporting @ramsestom.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

newbreedofgeek picture newbreedofgeek  Â·  6Comments

sibelius picture sibelius  Â·  4Comments

casoetan picture casoetan  Â·  5Comments

alexicum picture alexicum  Â·  6Comments

timdoes picture timdoes  Â·  4Comments