I am a complete noob to React Native and I am just trying to get hang of it.
I have a simple Login screen which I have developed using Container/Presentation component concept. My presentation component only has render function that renders three two TextInput and a Button.
Rather than displaying errors using Toasts I want to display errors below the TextInput itself. So, what I have done is added Text element below the TextInput. Something like below.
<TextInput placeholder="Email"></TextInput>
<Text ref="emailErrors"></Text>
By default, Text with ref emailErrors is hidden. But when the focus shifts from Email TextInput and if email is invalid for some reason I want to generate a simple error and set it as text for Text element with ref emailErrors.
Now, I understand that I will have to write my logic in container components and pass it as prop to presentation component. But what I am unable to understand is how to trigger the setting of error text and displaying the Text element.
UPDATE:
My Presentation Component:
class LoginForm extends Component {
constructor(props) {
super(props);
}
render() {
return (
<KeyboardAvoidingView
style={styles.loginFormContainer}>
<ScrollView
scrollEnabled={true}
enableOnAndroid={true}
enableAutomaticScroll={true}
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="always"
resetScrollToCoords={{ x: 0, y: 0 }}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}>
<View style={styles.loginForm}>
<Image
source={require("../../../assets/images/logos/logo.png")}
style={styles.logoImage}></Image>
<View style={styles.textInputWithIcon}>
<Image
style={styles.textInputIcon}
source={require("../../../assets/images/icons/email.png")}
></Image>
<View style={styles.textField}>
<TextInput name="email"
ref="email"
placeholder="Email"
blurOnSubmit={false}
returnKeyType={"next"}
underlineColorAndroid={COLORS.red}
onSubmitEditing={() => this.refs.password.focus()}
style={[GLOBALSTYLES.textInput, styles.textInput]}
onChangeText={(text) => this.props.onEmailTextChanged(text)}
>
</TextInput>
<Text
ref="email">
</Text>
</View>
</View>
<View style={styles.textInputWithIcon}>
<Image
style={styles.textInputIcon}
source={require("../../../assets/images/icons/locked.png")}
></Image>
<View style={styles.textField}>
<TextInput name="password"
ref="password"
blurOnSubmit={false}
placeholder="Password"
secureTextEntry={true}
returnKeyType={"next"}
style={styles.textInput}
underlineColorAndroid={COLORS.red}
onSubmitEditing={() => Keyboard.dismiss()}
style={[GLOBALSTYLES.textInput, styles.textInput]}
onChangeText={(text) => this.props.onPasswordTextChanged(text)}
onBlur={() => this.props.onPasswordTextBlurred()}
></TextInput>
<Text
ref="password">
</Text>
</View>
</View>
<TouchableOpacity
activeOpacity={0.5}
onPress={() => { this.props.onLoginPressed() }}
style={styles.loginButton}
underlayColor={COLORS.white}>
<Text style={styles.loginButtonText}>Login</Text>
</TouchableOpacity>
</View>
</ScrollView>
</KeyboardAvoidingView>
)
};
};
export default LoginForm;
My Container Component:
class Login extends Component {
static navigationOptions = ({ navigation }) => ({
"title": "Login"
});
constructor(props) {
super(props);
this.state = {}
}
// onLoginPressed will trigger the authentication workflow with the remote server.
onLoginPressed() {
const { isUserLoggedIn, email, password } = this.state;
if (this.state.isEmailValid && this.state.isPasswordValid) {
axios.post(CONFIGURATION.LOGIN_URL, {
username: email,
password: password
}).then(response => {
const navigationParams = {
baseUrl: response.data.url,
token: response.data.token,
username: email
}
this.props.dispatch(loginSuccess(navigationParams));
// Adding retrieved values to AsyncStorage
AsyncStorage.multiSet(
[
[IS_USER_LOGGED_IN, "YES"],
[USER, email],
[TOKEN, response.data.token],
[BASE_URL, response.data.url]
],
() => {
this.props.navigation.navigate("WebApp", navigationParams);
});
}).catch(error => {
console.error(error);
ToastAndroid.show("Authentication Failed", ToastAndroid.SHORT);
});
}
}
// Updating the state key email
onEmailTextChanged(text) {
this.setState({ "email": text });
}
// Updating the state key password
onPasswordTextChanged(text) {
this.setState({ "password": text });
}
onEmailTextBlurred() {
var text = this.state.email;
console.warn(text);
if (text == undefined || text.trim().length == 0) {
this.setState({ "isEmailValid": false });
this.setState({ "emailErrorMessage": "Email cannot be empty" });
}
else {
var regex = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
var isEmailValid = regex.test(text);
if (!isEmailValid) {
this.setState({ "isEmailValid": false });
this.setState({ "emailErrorMessage": "Email is incorrect." });
}
else {
this.setState({ "isEmailValid": true });
}
}
}
onPasswordTextBlurred() {
var text = this.state.password;
if (text == undefined || text.trim().length == 0) {
this.setState({ "isPasswordValid": false });
this.setState({ "passwordErrorMessage": "Password cannot be empty" });
}
else {
this.setState({ "isPasswordValid": true });
}
}
// rendering the LoginForm (presentational component) corresponding to this container component
render() {
return (
<LoginForm
onLoginPressed={() => this.onLoginPressed()}
onEmailTextChanged={(text) => this.onEmailTextChanged(text)}
onPasswordTextChanged={(text) => this.onPasswordTextChanged(text)}
onEmailTextBlurred={() => this.onEmailTextBlurred()}
onPasswordTextBlurred={() => this.onPasswordTextBlurred()}
/>
)
}
}
const mapStateToProps = (state) => {
return state;
}
const mapDispatchToProps = (dispatch) => {
const boundActionCreators = bindActionCreators(loginSuccess, dispatch);
return { ...boundActionCreators, dispatch };
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
You can use a flag in state of the component to check if invalid input is given by checking the status of the flag.
For example:
emailValidationRegex(v){
// use regex to check if email is valid and return true if email is valid, false if email is invalid
}
<TextInput
placeholder="Email"
onChange={(v)=>{this.setState({
email: v,
isEmailInvalid: !emailValidationRegex(v)
})}} >
</TextInput>
{this.state.isEmailInvalid && <Text>Sorry! Invalid Email</Text>}
Explanation:
isEmailInvalid is keeping the status of given email address if it is valid or not. Depending on it's status the following Error is shown conditionally.
Update:
In the onEmailTextChanged method in your container component update another state to hold if the email is valid or not:
onEmailTextChanged(text) {
this.setState({ "email": text, isEmailInvalid: !emailValidationRegex(text) });
}
Then pass this.state.isEmailInvalid in the props sent to the presentational component. Show error conditionally in the presentational component then.
render method of container component:
render() {
return (
<LoginForm
onLoginPressed={() => this.onLoginPressed()}
onEmailTextChanged={(text) => this.onEmailTextChanged(text)}
isEmailInvalid={this.state.isEmailInvalid}
onPasswordTextChanged={(text) => this.onPasswordTextChanged(text)}
onEmailTextBlurred={() => this.onEmailTextBlurred()}
onPasswordTextBlurred={() => this.onPasswordTextBlurred()}
/>
)
Now you are able to use this.props.isEmailInvalid in the presentational component
Related
I am trying to filter out pokemon in my searchbar component however when I type into the search bar the name of the component, nothing happens to the list. I have been searching online for solutions but other examples are too complex to implement into my code. I am consoling.log the input from the search bar component and it logs the input text. But just dont know how to filter out the pokemon. If anyone can help me I will really appreciate it!
// Home.js(Where pokemon ifo is coming from in the componentDidiMount abd then I pass down a function to the searchbar component)
import React, { useState } from "react";
import { View, Text , Button, FlatList, ActivityIndicator, TouchableOpacity } from "react-native";
import { GlobalStyles } from "../styles/GlobalStyles";
import PokeDetails from "./PokeDetails";
import SearchBarComponent from "../components/SearchBar";
import PokeBanner from "../components/PokeBanner";
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
dataSource: [],
filteredPokemon:[]
}
}
componentDidMount() {
fetch(`https://pokeapi.co/api/v2/pokemon/?limit=27`)
.then((res)=> res.json())
.then((response)=> {
this.setState({
isLoading: false,
dataSource: response.results,
})
console.log("RESPONSE",response)
console.log("RESPONSE.RESSSULTS",response.results)
})
}
filterPokemon =(textToSearch)=> {
const allPokemon = [...this.state.dataSource];
this.setState({
dataSource: allPokemon.filter(pokemon=> pokemon.name.toLowerCase().includes(textToSearch.toLowerCase()))
});
console.log("TextToSearch",textToSearch)
}
render() {
const showIndicator = this.state.isLoading == true ? <ActivityIndicator size="large" color="#0000ff" /> : null;
return(
<View style={GlobalStyles.container}>
<SearchBarComponent filterPoke={this.filteredPokemon} style={GlobalStyles.searchBar}/>
<PokeBanner/>
<View style={GlobalStyles.activityIndicator}>{showIndicator}</View>
<View style={GlobalStyles.pokeFlatList}>
<FlatList
contentContainerStyle={{paddingBottom: 70}}
keyExtractor={(item, index) => item.name}
numColumns={3}
data={this.state.dataSource}
renderItem={({item})=>
<View style={{flex: 1,justifyContent:"center", alignItems:"center", flexDirection: "row", marginBottom: 50, padding: 10}}>
<TouchableOpacity onPress={()=> this.props.navigation.navigate('PokeDetails',
{item ,imageUrl: `https://projectpokemon.org/images/normal-sprite/${item.name}.gif`})}>
<PokeDetails imageUrl={`https://projectpokemon.org/images/normal-sprite/${item.name}.gif`} name={item.name}/>
</TouchableOpacity>
</View>
}/>
</View>
</View>
)
}
}
export default Home;
// SearchBarComponent(Where I take the function passed down as a prop and use it in the updateSearch method)
import React from "react";
import {View, StyleSheet } from "react-native";
import { SearchBar } from 'react-native-elements';
import { GlobalStyles } from "../styles/GlobalStyles";
class SearchBarComponent extends React.Component {
state = {
search: '',
};
updateSearch=()=> {
this.props.pokeFilter(this.state.search);
console.log(this.state.search)
}
render() {
const { search } = this.state;
console.log(search)
return (
<View style={GlobalStyles.searchBar}>
<SearchBar
placeholder="Search pokemon..."
onChangeText={text=>this.setState({search: text})}
value={search}
/>
</View>
);
}
}
export default SearchBarComponent;
[![enter image description here][1]][1]
You need to call your updateSearch function when the user wants to search for a pokemon.
There are multiple ways to do that such as you can keep a separate button to handle submit function or call updateSearch inside onChangeText of your search bar component as below,
<SearchBar
placeholder="Search pokemon..."
onChangeText={this.updateSearch}
value={search}
/>
now change your updateSearch to handle serach text
updateSearch = (text) => {
this.setState({ search: text });
this.props.pokeFilter(this.state.search);
}
Also change the props of SearchBarComponent component as (make sure to use correct name)
<SearchBarComponent pokeFilter={this.filterPokemon} style={GlobalStyles.searchBar}/>
But you have to keep a temp variable to store all your pokemons. Because you need to filter data from all pokemons when user midified the search field.
componentDidMount() {
fetch(`https://pokeapi.co/api/v2/pokemon/?limit=27`)
.then((res) => res.json())
.then((response) => {
this.setState({
isLoading: false,
// keep a temp to store all pokemons
pokemons: response.results,
dataSource: response.results,
});
});
}
Now you can use your filter function
filterPokemon = (textToSearch) => {
// load all pokemons from temp
const allPokemon = [...this.state.pokemons];
this.setState({
dataSource: allPokemon.filter(pokemon => pokemon.name.toLowerCase().includes(textToSearch.toLowerCase()))
});
}
Hope this helps you. Feel free for doubts.
You should set FilteredPokemon as all pokemon when you do the first petition and pass that state to the FlatList. That way you will only show the filtered pokemon:
Then when you modify the search you will just filter on the allPokemon state and set it to the filtered. Let me just show it:
componentDidMount() {
fetch(`https://pokeapi.co/api/v2/pokemon/?limit=27`)
.then((res)=> res.json())
.then((response)=> {
this.setState({
isLoading: false,
dataSource: response.results,
filteredPokemon: response.results,
})
console.log("RESPONSE",response)
console.log("RESPONSE.RESSSULTS",response.results)
})
}
filterPokemon =(textToSearch)=> {
const allPokemon = [...this.state.dataSource];
const filteredPokemon = allPokemon.filter((pokemon) =>
pokemon.name.toLowerCase().includes(textToSearch.toLowerCase()))
this.setState({
filteredPokemon
});
console.log("TextToSearch",textToSearch)
}
Any problem just let me know and I will be happy to help!
I have the following react-native test code.
import { shallow } from 'enzyme';
import React from 'react';
import {
BorderlessButton,
InputBox,
ProgressBar,
} from 'components';
import Name from '../name.component';
describe('Name component', () => {
let wrapper: any;
const mockOnPress = jest.fn();
const mockSaveStep = jest.fn();
const mockProps: any = {
errors: null,
values: [{ givenName: 'givenName', familyName: 'familyName' }],
};
beforeEach(() => {
wrapper = shallow(<Name signUpForm={mockProps} saveStep={mockSaveStep} />);
});
it('should render Name component', () => {
expect(wrapper).toMatchSnapshot();
});
it('should render 2 <InputBox />', () => {
expect(wrapper.find(InputBox)).toHaveLength(2);
});
it('should render a <ProgressBar />', () => {
expect(wrapper.find(ProgressBar)).toHaveLength(1);
});
it('should render a <BorderlessButton /> with the text NEXT', () => {
expect(wrapper.find(BorderlessButton)).toHaveLength(1);
expect(wrapper.find(BorderlessButton).props().text).toEqual('NEXT');
});
it('should press the NEXT button', () => {
wrapper.find(BorderlessButton).simulate('click');
expect(mockOnPress).toHaveBeenCalled();
});
});
But the last test doesn't work properly. How can I simulate a this button click? This gives me an error saying
expect(jest.fn()).toHaveBeenCalled().
Expected mock function to have been called, but it was not called.
This is the component.
class NameComponent extends Component {
componentDidMount() {
const { saveStep } = this.props;
saveStep(1, 'Name');
}
disableButton = () => {
const {
signUpForm: {
errors, values,
},
} = this.props;
if (errors && values && errors.givenName && errors.familyName) {
if (errors.givenName.length > 0 || values.givenName === '') return true;
if (errors.familyName.length > 0 || values.familyName === '') return true;
}
}
handleNext = () => {
navigationService.navigate('PreferredName');
}
resetForm = () => {
const { resetForm } = this.props;
resetForm(SIGN_UP_FORM);
navigationService.navigate('LoginMain');
}
render() {
const { name, required } = ValidationTypes;
const { step } = this.props;
return (
<SafeAreaView style={{ flex: 1 }}>
<KeyboardAvoidingView style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : null}
enabled>
<ScreenContainer
navType={ScreenContainer.Types.LEVEL_THREE}
levelThreeOnPress={this.resetForm}>
<View style={styles.container}>
<View style={{ flex: 1 }}>
<SinglifeText
type={SinglifeText.Types.H1}
label='Let’s start with your legal name'
style={styles.textLabel}
/>
<View style={styles.names}>
<InputBox
name='givenName'
form={SIGN_UP_FORM}
maxLength={22}
placeholder='Given name'
containerStyle={styles.givenNameContainer}
inputContainerStyle={styles.inputContainer}
errorStyles={styles.inputError}
keyboardType={KeyBoardTypes.default}
validations={[required, name]}
/>
<InputBox
name='familyName'
form={SIGN_UP_FORM}
maxLength={22}
placeholder='Family name'
inputContainerStyle={styles.inputContainer}
errorStyles={styles.inputError}
keyboardType={KeyBoardTypes.default}
validations={[required, name]}
/>
</View>
<SinglifeText
type={SinglifeText.Types.HINT}
label='Please use the same name you use with your bank'
style={styles.hint}
/>
</View>
</View>
</ScreenContainer>
<ProgressBar presentage={(step / MANUAL_SIGNUP_STEP_COUNT) * 100} />
<View style={styles.bottomButtonContainer}>
<BorderlessButton
text='NEXT'
disabled={this.disableButton()}
onPress={this.handleNext}
/>
</View>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
}
How can I solve this??
You create the function mockOnPress(), but mockOnPress() is never injected into the component.
In the component you wrote, NameComponent has a child BorderlessButton component, in which the line, onPress={this.handleNext} is hard-coded in. handleNext() is defined elsewhere as:
handleNext = () => {
navigationService.navigate('PreferredName');
}
To test that the functionality of the button is working, I see two viable options. One is to use dependency injection. Instead of hard-coding the button to call navigationService.navigate('PreferredName'), you could have it execute code that is passed in as a prop. See the following as an example:
it('Button should handle simulated click', function (done) {
wrappedButton = mount(<MyButton onClick={() => done()}>Click me!</BaseButton>)
wrappedButton.find('button').simulate('click')
}
Note that you could take the principle provided in the above example and expand it to your example by passing in the functionality you want to occur onClick as a prop to your NameComponent.
Another option you have, is to test whether clicking the button causes the side effects you want to occur. As written, pressing the button should call, navigationService.navigate('PreferredName'). Is this the intended effect? If so, you can change your test to validate whether navigationService.navigate('PreferredName') was called somehow.
I have two components who use the same reducer and share the same state.
The first one is a form, the second one is a separate component to
update a specific field of that form using
react-native-autocomplete-select.
In the second component, everything works fine. But when I get back
to the first component (the form), the prop that I'm updating in the
second component is now undefined. Only when I leave the component
and come back to it or reload my app does the component display the
correct value.
I'm new to redux and I thought I had figured it out but apparently, I'm still missing something.
I'll try to share as much code as possible in order to make it easy for anyone to help me out but let me know if you want me to share additional code.
I would really like to understand what's going on.
First Component
class EditElem extends Component {
componentWillMount() {
this.props.xFetch();
}
onButtonPress() {
const { name, description, elem_id } = this.props;
this.props.xSave({ name, description, elem_id });
}
render() {
return (
<ScrollView>
<View>
<Text>Information</Text>
<CardSection>
<Input
label="Name"
placeholder="..."
value={this.props.name}
onChangeText={value => this.props.xUpdate({ prop: 'name', value })}
/>
<Text style={styles.labelStyle}>Description</Text>
<Input
placeholder="Write here..."
value={this.props.description}
onChangeText={value => this.props.xUpdate({ prop: 'description', value })}
multiline = {true}
numberOfLines = {4}
/>
<TouchableWithoutFeedback onPress={ () => Actions.selectElem() }>
<View style={styles.wrapperStyle}>
<View style={styles.containerStyle}>
<Text style={styles.labelStyle}>Elem</Text>
<Text adjustsFontSizeToFit style={styles.inputStyle}>{checkElem(this.props.elem_id ? this.props.elem_id.toString() : "0")}</Text>
</View>
</View>
</TouchableWithoutFeedback>
</CardSection>
<Button title="Save Changes" onPress={this.onButtonPress.bind(this)} />
</View>
</ScrollView>
);
}
}
const mapStateToProps = (state) => {
const { name, description, elem_id } = state.x.x;
return { name, description, elem_id };
};
export default connect(mapStateToProps, { xUpdate, xFetch, xSave })(EditElem);
Second Component
class SelectElem extends Component {
componentWillMount() {
this.props.xFetch();
}
saveElem(suggestion) {
let elem_id = suggestion.id;
let text = suggestion.text
this.props.xUpdate({ prop: 'elem', text })
this.props.xUpdate({ prop: 'elem_id', elem_id })
this.props.xSave({ elem_id });
}
render() {
const suggestions = data
const onSelect = (suggestion) => {
this.saveElem(suggestion);
}
return(
<View style={{flex: 1}}>
<AutoComplete
placeholder={checkElem(this.props.elem_id ? this.props.elem_id.toString() : "0")}
onSelect={onSelect}
suggestions={suggestions}
suggestionObjectTextProperty='text'
value={this.props.elem}
onChangeText={value => this.props.xUpdate({ prop: 'elem', value })}
minimumSimilarityScore={0.4}
/>
</View>
)
}
}
const mapStateToProps = (state) => {
const { elem_id, description, name, elem } = state.x.x;
return { elem_id, description, name, elem };
};
export default connect(mapStateToProps, { xUpdate, xFetch, xSave })(SelectElem);
store
const store = createStore(reducers, {}, compose(applyMiddleware(ReduxThunk)));
reducer
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_X:
return { ...state, x: { ...state.x, name: action.payload.name, description: action.payload.description, elem_id: action.payload.elem_id } };
case UPDATE_X:
return { ...state, x: { ...state.x, [action.payload.prop]: action.payload.value }};
case SAVE_X:
return state;
default:
return state;
}
}
Actions
export const xUpdate = ({ prop, value }) => {
return {
type: UPDATE_X,
payload: { prop, value }
};
};
export const xSave = ({ name, description, elem_id }) => {
return (dispatch) => {
axios({
method: 'post',
url: 'https:xxxxxxxxxxxxxxxxxxxx',
data: {_____________________ }
}
}).then(response => {
dispatch({ type: SAVE_X });
}).catch(error => console.log(error))
};
};
Can you check if UPDATE_X, SAVE_X ... are defined? Do you have the right import statement at the top of the file?
Ok so my problem came from my reducer actualy in my SAVE_X:
I had:
case SAVE_X:
return { state };
Instead of this:
case SAVE_X:
return { ...state, elem: { ...state.elem, elem_id: action.payload.elem_id } };
Anyone know how to auto submit a Redux Form in React Native when certain conditions are met? My attempt below is throwing a warning.
In the doc there is an example for remote submitting, but it's using HTML form's <form onSubmit={handleSubmit}>. What is the equivalent of this in React Native?
My attempt: Submit when the form's input length >= 2
class MyClass extends React.Component {
constructor(props) {
super(props);
this.handlSubmitWrapper = this.handlSubmitWrapper.bind(this);
}
handlSubmitWrapper() {
const { handleSubmit } = this.props;
handleSubmit(() => console.log('submitting...'))();
}
getTextInput({ input: { onChange, value, ...otherProps }, autoSubmit }) {
if (value && value.length > 1) {
// triger submit
autoSubmit();
}
return (
<TextInput
onChangeText={onChange}
style={{height: 50, backgroundColor: '#666'}}
{...otherProps}
maxLength={2}
/>
);
}
render() {
return (
<View>
<Field name="myText"
component={this.getTextInput}
autoSubmit={this.handlSubmitWrapper}
/>
</View>
);
}
}
const MyForm = reduxForm({
form: 'setupPasscode',
})(MyClass);
export default connect()(MyForm);
warning:
ExceptionsManager.js:71 Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
You are calling the submit action when rendering a component. You cannot do this with react. You should instead use the TextInput onChange method to achieve that.
class MyClass extends React.Component {
constructor(props) {
super(props);
this.handlSubmitWrapper = this.handlSubmitWrapper.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
}
handlSubmitWrapper() {
const { handleSubmit } = this.props;
handleSubmit(() => console.log('submitting...'))();
}
handleInputChange(event) {
const newText = event.nativeEvent.text;
if (newText && newText.length > 1) {
// triger submit
this.handlSubmitWrapper();
}
}
getTextInput({ input: { onChange, value, ...otherProps } }) {
return (
<TextInput
onChange={this.handleInputChange}
onChangeText={onChange}
style={{height: 50, backgroundColor: '#666'}}
{...otherProps}
maxLength={2}
/>
);
}
render() {
return (
<View>
<Field name="myText" component={this.getTextInput} />
</View>
);
}
}
const MyForm = reduxForm({
form: 'setupPasscode',
})(MyClass);
export default connect()(MyForm);
I am trying to build a simple login form in React-Native using the tcomb-form-native component. I've created the method handleChange for setting the values to the initial state and the handleForm for submiting. Right now with my code when I'm typing something into the input fields the content is deleted and is replaced with the placeholder (no console output) and when I press the submit button im getting the error "undefined is not an object (evaluating this.refs.form)". Is there a better way how to do it?
Here is how I imported and set up everything:
import React, { Component } from 'react';
import {
Text,
View,
StyleSheet,
TextInput,
TouchableHighlight,
ActivityIndicator,
Image
} from 'react-native';
var t = require('tcomb-form-native');
var Form = t.form.Form;
var User = t.struct({
username: t.String,
password: t.String
});
var options = {
auto: 'placeholders',
fields: {
password: {
password: true
}
}
};
Here are my class and methods:
class Login extends Component {
constructor(props) {
super(props);
this.state = {
value: {
username: '',
password: ''
},
isLoading: false,
error: false
};
this.handleChange.bind(this);
}
handleChange(value) {
this.setState({value});
}
handleSubmit() {
var value = this.refs.form.getValue();
//update the indicator spinner
this.setState({
isLoading: true
});
console.log(value);
}
And this is how im rendering everything:
render() {
return (
<View style={styles.mainContainer}>
<View style={styles.logo}>
<Image source={require('../icon.png')} />
</View>
<Text style={styles.title}>Login</Text>
<Form
ref={(f) => this.form = f}
type={User}
options={options}
onChangeText={(value) => this.setState({value})}
value={this.state.value}
/>
<TouchableHighlight
style={styles.button}
onPress={this.handleSubmit}
underlayColor="white">
<Text style={styles.buttonText}>LOGIN</Text>
</TouchableHighlight>
</View>
);
}
Don't Use Bind When Passing Props
then you can do like this
onPress={this.handleSubmit}
handleSubmit = () => {...}
In TouchableHightlight, you need to define: onPress={this.handleSubmit} as:
onPress={() => this.handleSubmit()} or
onPress={this.handleSubmit.bind(this)}
In addition, ref should not be string nowadays in React (+ Native) (https://facebook.github.io/react/docs/more-about-refs.html#the-ref-string-attribute), but e.g.:
ref={(f) => this.form = f}
and then you refer to it with this.form.