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....
Related
I've tried to console.log the value of the text input but I get the error "undefined is not an object (evaluating 'this.state.inputValue')". What's the problem? Thank you!
class SearchScreen extends React.Component {
state = {
inputValue: "",
};
search() {
console.log(this.state.inputValue);
}
render() {
return (
<View>
<TextInput
onChangeText={
((inputValue) => this.setState({ inputValue }),
this.search)
}
value={this.state.inputValue}
/>
</View>
);
}
}
export default SearchScreen;
The problem is in the way you've implemented it. Please try as below...
class SearchScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
inputValue: '',
};
}
search() {
console.log(this.state.inputValue);
}
render() {
return (
<View>
<TextInput
onChangeText={(inputValue) => {
this.setState({ inputValue });
this.search();
}}
value={this.state.inputValue}
/>
</View>
);
}
}
export default SearchScreen;
This problem occurred because two things.
First:
The this.setState is a async function.
If you pass a function after the setState this will work like a .then() in a promisse.
Second:
If you pass one function after another separating them by ',' the rightmost function will be executed first
You can resolve this doing something like that:
onChange={ inputValue => {
this.setState({ inputValue });
this.search();
}}
Or you can try something like that:
class SearchScreen extends React.Component {
state = {
inputValue: "",
};
search = () {
console.log(this.state.inputValue);
}
setSearch = inputValue => {
// The function 'search' will be execute after the state was set
this.setState(
{ inputValue },
() => this.search()
);
}
render() {
return (
<View>
<TextInput
onChangeText={ inputValue => this.setSearch(inputValue) }
value={this.state.inputValue}
/>
</View>
);
}
}
export default SearchScreen;
You didn't set the value of state property. provide a value to setState.
this.setState({property : value})
The problem is this line:
onChangeText={
((inputValue) => this.setState({ inputValue }),
this.search)
}
You can use the short function notation only when your function has one statement:
(inputValue) => this.setState({ inputValue })
You actualy have 2 statements though, so you need to create a full function block using {}
(inputValue) => {
this.setState({ inputValue })
this.search()
}
I have a function that render LoginPage if the user is not logged and render the IndexPage if is logged, but It is not rendering none, I tried alerting the user.displayName and It work. See my code.
renderPage = () => {
firebase.auth().onAuthStateChanged(user => {
if (user) {
return <IndexPage />;
} else {
return <LoginPage />;
}
});
};
render() {
return <div>{this.renderPage()}</div>;
}
Why is not working?
You miss a return in the renderPage function, but performing async requests in render is not a good approach in react.
What you should do, is to move the user into the state, then on componentDidMount fetch the user from your async code, and inside your render use the state prop user.
So your code should be something like:
constructor() {
this.state = { user: null };
}
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
user ? this.setState({ user }) : this.setState({ user: null });
});
}
render() {
const content = this.state.user ? <IndexPage /> : <LoginPage />;
return <div>{content}</div>;
}
Your function inside render method is async function, what you get is undefined.
You should store the user state. Do something like,
class YourComponent extends Component {
constructor(props) {
super(props);
this.state = {
user: null
};
}
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.setState({
user
});
}
});
}
render() {
return (
{this.state.user ? <IndexPage /> : <LoginPage />}
);
}
}
I know there's many similar threads but since I have a hard time understanding the answers I figured I might try with my own code and see if I can make any sense of the answers.
I just set this up really simple to test it out. I have an Index file that is opened when I start the app. In the index I have testValue in this.state:
Update:
In SignIn:
constructor(props){
super(props);
this.state={
credentials: {
username: "",
password:"",
}
}
this.navigate = this.props.navigation.navigate;
{...}
this.navigate("main", {
credentials: this.state.username,
});
In main:
constructor(props) {
super(props);
this.params = this.props.navigation.state.params;
this.navigate = this.props.navigation.navigate;
{...}
render() {
console.log(this.params.username);
This would actually log the testValue. All you have to do is to pass the testValue state as a prop in TestIndex component. Like this:
Index.jsx
export default class Index {
constructor(props) {
super(props);
this.state = {
testValue: 'hello'
}
}
render() {
return <TestIndex testValue={this.state.testValue} />
}
}
TestIndex.jsx
export default class TestIndex extends React.Component {
index() {
this.props.navigation.navigate("index")
}
handleClickMain = () => {
this.props.navigation.navigate("index");
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity style={styles.btn} onPress={() => {
console.log(this.props.testValue) //here's where I want it to log testValue from the index file
this.handleClickIndex()
}
}>
</TouchableOpacity>
</View>
);
}
}
I have parent component and child(listView) component. My target is to send backend dispatched data from parent to child. I achieve that via button click. Problem is that child renders after second parent button click. Maybe my mistake is somewhere in componentWillReceiveProps?
Parent:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: '',
noRepos: false
};
}
componentWillReceiveProps(newProps) {
this.setState({
dataSource: newProps.data
});
this.validateData(newProps)
}
submitUsername() {
if(this.validateInput()){
this.props.dispatch({type: 'DUMMY', state: this.state.username});
} else {
this.setState({emptyInput: true});
}
}
render() {
return (
...
<View>
<ListViewComponent dataRecieved={this.state.dataSource} />
</View>
...
);
}
}
export default connect(state => {
return {
data: state.data,
};
})(Parent);
Child:
export default class ListViewComponent extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2,
}),
};
}
propTypes: {
dataRecieved: PropTypes.func.string
};
componentWillReceiveProps(newProps) {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(this.props.dataRecieved),
});
}
renderRow(rowData) {
return (
<View>
<Text>{rowData.name}</Text>
</View>
);
}
render() {
return (
<View>
<ListView dataSource={this.state.dataSource}
enableEmptySections={true}
renderRow={this.renderRow} />
</View>
);
}
}
Alright, a general advice is to always keep a single source of truth:
do not copy the data that you already have in props to your internal component state. Use the data in props.
try to create your components as stateless as possible (see above...use props, or have the component listen to a 'store'. See Redux or AltJs).
Specifically to try to solve your issue:
In parent replace:
<ListViewComponent dataRecieved={this.state.dataSource} />
with
<ListViewComponent dataRecieved={this.props.data} />
And in ListViewComponent, don't do:
this.setState({
dataSource: this.state.dataSource.cloneWithRows(this.props.dataRecieved),
});
but do:
render() {
var ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2,
})
, dataSource = ds.cloneWithRows(this.props.dataRecieved);
return (
<View>
<ListView dataSource={dataSource}
enableEmptySections={true}
renderRow={this.renderRow} />
</View>
);
}
The above is untested code, but should serve as a guide to what approach to follow.
The way you originally implemented ListViewComponent is fine, and you SHOULD be using componentWillReceiveProps when refreshing your ListView. Every best practice out there says to do this (just Google react native redux listview). You just have a slight error in your implementation that I mentioned in a comment above. Also, you should not be recreating the ListView.DataSource inside the render function, that is not good for performance and defeats the purpose of rowHasChanged in ListView.DataSource.
The error that I'm talking about is here:
componentWillReceiveProps(newProps) {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(this.props.dataRecieved),
});
}
it should be:
// ListViewComponent
componentWillReceiveProps(newProps) {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(newProps.dataRecieved),
});
}
Also, your Parent should not be holding a dataSource in its state, just pass data straight down to ListViewComponent because Redux is passing it as a prop already:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
// ...
};
}
componentWillReceiveProps(newProps) {
// ...
}
submitUsername() {
if (this.validateInput()) {
this.props.dispatch({ type: 'DUMMY', ... });
} else {
// ...
}
}
render() {
return (
...
<View>
{/* this.props.data automatically comes from the `connect` wrapper below */}
<ListViewComponent dataRecieved={this.props.data} />
</View>
...
);
}
}
export default connect(state => {
return {
data: state.data,
};
})(Parent);
You should also take a look at this gist. It is an example of how to use Redux alongside a ListView. His example uses cloneWithRowsAndSections, but because you don't have sections, you just adapt with cloneWithRows instead. This gist was written by a pretty active core React Native developer.
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.