tcomb-form-native
tcomb-form-native copied to clipboard
Avoid showing error messages while getting the value from tform.getValue()
Version
Tell us which versions you are using:
- tcomb-form-native v0.6.11
- react-native version
https://github.com/expo/react-native/archive/sdk-25.0.0.tar.gz
Expected behaviour
I have 2 textboxes
- Password
I wanna disable the button until the user enters valid email and password. So, for that in the onChange
method, I retrieve the value from form.getValue()
which triggers validation as stated in the docs. If the value is null, I change the button's disabled state to true
else false
. I only want to show errors when the input loses focus or on submit and not on each getValue()
call.
Actual behavior
As soon as onChange
is triggered, the form starts showing error in all the fields because I call form.getValue()
. I want to show the error only when the user moves to the next textbox (focus is lost from the current one) or if the user hits Submit. I don't wanna pester him with error messages when the user just starts typing in the textbox.
Steps to reproduce
- Create 2 form fields (email and password with refinements) with option containing error messages
- Retrieve the value in the
onChange
method - You'll start seeing error messages.
Sample code
// @flow
import React, { PureComponent } from 'react';
import { View, Text, Platform, TouchableHighlight } from 'react-native';
import t from 'tcomb-form-native';
import { resetTo } from 'src/lib/navigation';
import { SIGNED_IN } from 'src/routes/constants';
import { getErrorMessage } from 'src/lib/auth-helpers';
import { FullScreenBGImage } from 'src/components';
import { text, background } from 'src/styles/';
import LoginBG from '../../../assets/images/login-bg.jpg';
import styles from './style';
type Props = {
loggedIn: boolean,
navigation: Object,
login: (string, string) => void,
user: Object,
};
type States = {
isDisabled: boolean,
value: ?Object,
};
const Email = t.refinement(t.String, email => {
const reg = /[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 reg.test(email);
});
const { Form } = t.form;
// here we define our domain model
const LoginForm = t.struct({
email: Email,
password: t.String,
});
const textboxStyle = {
color: text.color2,
backgroundColor: background.color1,
fontSize: 17,
height: 50,
paddingVertical: Platform.OS === 'ios' ? 7 : 0,
paddingHorizontal: 16,
borderWidth: 1,
marginBottom: 5,
};
const formStylesheet = {
...Form.stylesheet,
textbox: {
normal: {
...textboxStyle,
},
error: {
...textboxStyle,
},
},
errorBlock: {
color: text.error,
},
};
const formOptions = {
auto: 'placeholders',
fields: {
email: { error: 'Enter valid email' },
password: {
error: 'Enter valid password'
password: true,
secureTextEntry: true,
},
},
stylesheet: formStylesheet,
};
class Login extends PureComponent<Props, States> {
loginForm: ?Object;
onFormChange: () => void;
static navigationOptions = {
header: null,
};
constructor(props: Props) {
super(props);
this.loginForm = {};
this.state = {
value: null,
isDisabled: true,
};
this.handleSubmit = this.handleSubmit.bind(this);
this.onFormChange = this.onFormChange.bind(this);
}
/**
* ComponentWillReceiveProps.
*
* Redirect if user is logged in
*/
componentWillReceiveProps(nextProps: Props) {
if (nextProps.loggedIn !== this.props.loggedIn && nextProps.loggedIn) {
resetTo(SIGNED_IN, nextProps.navigation);
}
}
handleSubmit = () => {
// use that ref to get the form value
const value = this.loginForm ? this.loginForm.getValue() : null;
if (value) {
this.props.login(value.email, value.password);
}
}
onFormChange() {
const value = this.loginForm ? this.loginForm.getValue() : null;
if (value) {
this.setState({
value,
isDisabled: false,
});
}
}
render() {
const errorMessage = getErrorMessage(this.props.user);
const error = errorMessage
? <View><Text style={styles.errorMessage}>{errorMessage}</Text></View>
: null;
return (
<View style={styles.container}>
<FullScreenBGImage imageSrc={LoginBG} styles={styles.bgImage}>
<View style={styles.logo}>
<Text style={styles.logoLabel}>VERUS</Text>
</View>
<View style={styles.loginForm}>
{error}
<Form
ref={c => { this.loginForm = c; }}
type={LoginForm}
value={this.state.value}
onChange={this.onFormChange}
options={formOptions} // pass the options via props
/>
<TouchableHighlight
style={styles.button}
onPress={this.handleSubmit}
underlayColor="#99d9f4"
disabled={this.state.isDisabled}
>
<Text style={styles.buttonText}>Sign In</Text>
</TouchableHighlight>
</View>
</FullScreenBGImage>
</View>
);
}
}
export default Login;
If your issue is that .getValue()
on the main form also performs a[n unwanted] .validate()
you could work around it by calling the .getValue()
on the underlying "input" using the following:
onFormChange() {
//const value = this.loginForm.getValue();
const value = this.loginForm.refs.input.getValue();
}
I kind of agree that this is annoying and the above solution isn't really ideal. You kind of expect a call to form.validate()
to perform the validation and form.getValue()
to just return the value. Current functionality probably violates the principle of least astonishment (POLA).
I would also like to see a variant of validate()
without side effects, so that I can use the partial validation state to trigger the rendering of other controls outside of the form. I'd like to do this without triggering any error highlighting.
On looking into this a bit further, it turns out that you can re-use the validation rules on a copy of the underlying form data without triggering error highlighting:
import t from 'tcomb-form-native';
import { validate } from 'tcomb-validation';
const FormDetails = t.struct({
name: t.String,
address: t.String,
});
onChange = (value) => {
// Is not bound to the underlying form state, so error highlighting is not triggered
const valid = validate(value, FormDetails).isValid();
// do something that depends on the validity
}
This seems to be reasonably concise and flexible.
@0x6e6562 Interesting. I'll check it out.
I agree, could have a function that return the only data without call validate()
. Something like getPureValue()
.
@rashtay
But for now, you can use the own API:
this.refForm.pureValidate().value
This return Struct fields without show errors.