React-Native: Cannot set state from form values - javascript

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.

Related

How to filter pokemon in react native

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!

call function without onpress react native

I'm new to react native. I am trying to get 'Key' without using the onpress in button.
I just want to get a 'key', when i could open component. How it could be possible?
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
TextInput,
Button,
View,
AsyncStorage
} from 'react-native';
export default class History extends Component {
constructor(props) {
super(props);
this.state = {
myKey: null
}
}
async getKey() {
try {
const value = await AsyncStorage.getItem('#MySuperStore:key');
this.setState({ myKey: value });
} catch (error) {
console.log("Error retrieving data" + error);
}
}
render() {
return (
<View style={styles.container}>
<Button
style={styles.formButton}
onPress={this.getKey.bind(this)}
title="Get Key"
color="#2196f3"
accessibilityLabel="Get Key"
/>
<Text >
Stored key is = {this.state.myKey}
</Text>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
padding: 30,
flex: 1,
backgroundColor: '#F5FCFF',
},
});
I am able to get a key with onpress but i want without onpress. Please suggest.
You can simply get your key value with componentDidMount. As you should know, when the App runs and comes to the current screen, there is a flow of methods will be called before and after rendering the render function. So ComponentDidMount comes after render function is called. So since you need to only display the key value, just follow below code.
constructor(props) {
super(props);
this.state = {
myKey:''
}
}
getKey = async() => {
try {
const value = await AsyncStorage.getItem('#MySuperStore:key');
this.setState({ myKey: value });
} catch (error) {
console.log("Error retrieving data" + error);
}
}
componentDidMount(){
this.getKey();
}
render() {
return (
<View style={styles.container}>
<Button
style={styles.formButton}
onPress={this.getKey.bind(this)}
title="Get Key"
color="#2196f3"
accessibilityLabel="Get Key"
/>
<Text >
Stored key is {this.state.myKey}
</Text>
</View>
)
}
Whenever render function being called, in that time, still key value is not set. So, this.state.myKey would be just Stored key is. But after that, once the componentDidMount called, it sets the myKey value and change states. Which will trig render function to render everything again. That would show your key value within the text component eventually without touching any button.

React Native display validation errors inline

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

Avoid showing error messages while getting the value from TForm.getValue()

I have 2 textboxes
Email
Password
I want to disable the button until the user enters valid email and password. So, 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.
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.
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;
Version
tcomb-form-native v0.6.11
Hey passing by the method will always validate the form first so you should just change the method of onChange and get the value of the email field you want by using something like this.refs.loginForm.refs.input.refs.email.state.value where email is the name of your field and loginForm the name of the ref of your form. Then you can test the value with your regex by your own to change the state of the button ;)
Some docs that gave me the idea :
Here : https://github.com/gcanti/tcomb-form/issues/370
and here https://github.com/gcanti/tcomb-form/blob/master/GUIDE.md#accessing-fields

React Native "this.setState is not a function"

I'm starting with react native and trying to bind some actions to class methods but I'm getting some errors about methods not found.
I tried binding:
class AccountsScreen extends Component {
constructor (props) {
super(props)
this.userChange = this.userChange.bind(this)
this.state = {
user: '',
password: ''
}
}
render () {
return (
<View>
<Text>Pass</Text>
<TextInput
onChange={this.userChange}
/>
</View>
)
}
userChange (user) {
this.setState({user: user})
}
}
and arrow functions
class AccountsScreen extends Component {
constructor (props) {
super(props)
this.state = {
user: '',
password: ''
}
}
render () {
return (
<View>
<Text>Pass</Text>
<TextInput
onChange={(user) => this.userChange(user)}
/>
</View>
)
}
userChange (user) {
this.setState({user: user})
}
}
but I keep getting the same error:
"this.setState is not a function"
Totally stuck. Any ideas?
actually no need to make a function to set your state, you can just do this
<TextInput onChangeText={(user) => this.setState({user: user})} />
Actually it was a stupid mistake. I was using onChange instead of onChangeText method....

Categories