React Native Change State With Unexpected Logging - javascript

For context, I am working on this React Native Tutorial
The way this logs confuses me. The following is the console output when I changed an empty input field by typing "a" then "b".
Here's my SearchPage class. Why does console.log('searchString = ' + this.state.searchString); show the previous value for this.state.searchString?
class SearchPage extends Component {
constructor(props) {
super(props);
this.state = {
searchString: 'london'
};
}
onSearchTextChanged(event) {
console.log('onSearchTextChanged');
console.log('searchString = ' + this.state.searchString +
'; input text = ' + event.nativeEvent.text );
this.setState({ searchString: event.nativeEvent.text });
console.log('Changed State');
console.log('searchString = ' + this.state.searchString);
}
render () {
console.log('SearchPage.render');
return (
<View style={styles.container}>
<Text style = { styles.description }>
Search for houses to Buy!
</Text>
<Text style = {styles.description}>
Search by place name or search near your location.
</Text>
<View style={styles.flowRight}>
<TextInput
style = {styles.searchInput}
value={this.state.searchString}
onChange={this.onSearchTextChanged.bind(this)}
placeholder='Search via name or postcode'/>
<TouchableHighlight style ={styles.button}
underlayColor = '#99d9f4'>
<Text style ={styles.buttonText}>Go</Text>
</TouchableHighlight>
</View>
<TouchableHighlight style={styles.button}
underlayColor= '#99d9f4'>
<Text style={styles.buttonText}>Location</Text>
</TouchableHighlight>
<Image source={require('image!house')} style={styles.image}/>
</View>
);
}
}

setState can be an asynchronous operation, not synchronous. This means that updates to state could be batched together and not done immediately in order to get a performance boost. If you really need to do something after state has been truly updated, there's a callback parameter:
this.setState({ searchString: event.nativeEvent.text }, function(newState) {
console.log('Changed State');
console.log('searchString = ' + this.state.searchString);
}.bind(this));
You can read more on setState in the documentation.
https://facebook.github.io/react/docs/component-api.html

Related

Natigate to different screes within same page in React Native

I have made a main screen in which I have added three button in the header, on pressing I want to open three different screens respectively but its not working.
Here's what I've tried:
constructor(props) {
super(props);
this.state = {
initialstate: 0, //Setting initial state for screens
};
}
render(){
return(
<View style={styles.container}>
<TouchableOpacity onPress={() => this.setState({ initialstate: 0})}>
<Image source={require('../../assets/add.png')}
resizeMode="contain"/>
</TouchableOpacity>
<TouchableOpacity onPress={() => this.setState({ cardstate: 1})}>
<Image source={require('../../assets/request.png')}
resizeMode="contain"/>
</TouchableOpacity>
<TouchableOpacity onPress={() => this.setState({ cardstate: 2})}>
<Image source={require('../../assets/send.png')}
resizeMode="contain"/>
</TouchableOpacity>
{this.state.initialstate == 0 ? ( <RequestComp/> ) : ( <TopUpComp/> ) } //Over Here when I use the Third Screen like " : <SendComp/> " it gives me JXS error says "EXPECTED }"
</View>
The first problem is that you have an initialState state variable that is only updated by the first buttons and the other two are setting cardState so even if the ternary statement was formatted correctly it wouldn't have worked either way
But aside from this problem I don't recommend using a ternary for what you're trying to do, because the conditions become difficult to read.
There are multiple ways of doing this, but I like the approach of the accepted answer here React render various components based on three logic paths). The idea is to create an object that holds a mapping of strings to components. Then you can conditionally render an item based on the current key value.
Here's an example of how you could refactor your code to use this approach:
const tabComponents = {
request: <RequestComp />,
topUp: <TopUpComp />,
send: <SendComp />,
};
class CustomTabs extends React.Component {
constructor(props) {
super(props);
this.state = {
cardstate: 'request', // Setting initial state for screens
};
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity
onPress={() => this.setState({ cardstate: 'request' })}>
// Button content...
</TouchableOpacity>
<TouchableOpacity onPress={() => this.setState({ cardstate: 'topUp' })}>
// Button content...
</TouchableOpacity>
<TouchableOpacity
onPress={() => this.setState({ cardstate: 'send' })}>
// Button content...
</TouchableOpacity>
{tabComponents[this.state.cardstate]}
</View>
);
}
}

Update page data

I need your help. I have a problem with updating data on a page. Basically I have a homepage where there is data like "FirstName: ...", "LastName: ..." which are retrieved from the login.
Once the login has been completed, you will automatically be taken to the Homepage page each time the app is started.
In this way I retrieve the information on the Homepage page.
The problem is that the user can modify this data through a form (ModifyProfile), and once the data is done they are not updated.
How can I update them anyway?
Thank you.
Homepage.js
class HomepageUtente extends Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
const FirstName = global.utente.data.Person.FirstName;
const LastName = global.utente.data.Person.LastName;
return (
<View style={style.container}>
<View style={style.page}>
<Icon name="user-circle" color="#64c7c0" size={70} onPress={() => Actions.yourprofile({ cf: Username } )} />
<Text
style={{ textAlign: 'center', fontSize: 20, }}>{"Welcome"}
</Text>
<Text style={{ textAlign: 'center', fontSize: 20, color: '#64c7c0', fontWeight: 'bold' }}>
{FirstName} {LastName}
</Text>
<Text style={{ color: '#64c7c0', paddingTop: 20 }} onPress={() => Actions.ModifyProfile({ cf: Username })} >Modify Profile</Text>}
</View>
</View>
)
}
}
ModifyProfile
export default class ModificaProfilo extends Component {
constructor(props) {
super(props);
this.state = {};
}
findUtente(cf) {
//Search data cf in the db
//......
//......
.then(response => {
let utente = response.docs[0];
console.log("Utente: " + utente)
console.log("Sei qui 1")
utente.Person.FirstName = this.state.FirstName;
utente.Person.LastName = this.state.LastName;
global.utente.db.localdb().put(utente);
})
.catch(function(err) {
console.log(JSON.stringify(err));
})
}
render() {
return (
<View style={style.container}>
<View style={style.page}>
<KeyboardAwareScrollView>
<View style={style.inputContainer}>
<TextInput
style={style.inputs}
placeholder="Name"
placeholderTextColor="#64c7c0"
keyboardType="default"
underlineColorAndroid="grey"
onChangeText={FirstName =>
this.setState({ FirstName })
}
/>
</View>
<View style={style.inputContainer}>
<TextInput
style={style.inputs}
placeholder="Surname"
placeholderTextColor="#64c7c0"
keyboardType="default"
underlineColorAndroid="grey"
onChangeText={LastName =>
this.setState({ LastName })}
/>
</View>
<View style={style.footer}>
<TouchableOpacity
style={[style.button, style.buttonOK]}
onPress={() => this.findUtente(this.props.cf)}
>
<Text style={style.buttonTesto}>Modifica</Text>
</TouchableOpacity>
</View>
Actually, you can do that but you shouldn't. Let's solve it first.
The component in Homepage.js, we call it A ;
The component in ModifyProfile, we call it B ;
You need a reference to component A, and then call A.forceUpdate().
It means you add global.A = this in Homepage.js;
add global.A.forceUpdate() in ModifyProfile after you get the new data;
Why: React Component would reRender only if the state or props of the component changes, that's why you need to call forceUpdate to make component A reRender again unconditionally.
if your change the FirstName by global.utente.data.Person.FirstName = 'NewName', component A can not detect the change event.
By the way, you should use a state container like redux to help you, rather than a global variable. You can connect FirstName as your props.
I recommend dvajs which is easy to learn, you can just focus on the data and flow, you don't need to care about if a component should update most of the times.
And there is a starter of dvajs, you can just run it quickly followed by:
react-native-dva-starter
Forget it if I misunderstood your question.

React Native: In an array of Text Inputs which are looped then displayed, how to get nth element and modify those elements separately?

In this module I am trying to create a survey module similar to the one in twitter.
at first, color of text input borders are grey and when I focus (click) the text input, only one of them (clicked one) must be blue. Same idea when I type a text, they all shouldn't get the same value. I should be able to get each text input value that I created by clicking plus icon, as a String
Should I use a flatlist or a listview rather than a for loop ?
React-Native Listview, press row and change that row style
I also tried to solve it according to this example.
I change this example a little bit, I was able to change border color of clicked one. but still, I couldn't get the values...
Any solution suggestion ? Thank you.
screenshot 1
screenshot 2
This is my code;
changeInputBorderColor = () => {
const newinputBorderColor = cloneDeep(this.state.inputBorderColor);
newinputBorderColor.bar = '#04A5F5';
this.setState({inputBorderColor: {bar: newinputBorderColor.bar}});
};
changeInputBorderColor2 = () => {
this.setState({
inputBorderColor: {
bar: 'grey'
}
})
};
incrementInputCount = () => {
if (this.state.inputCounter < 5) {
this.setState(prevState => {
return {inputCounter: prevState.inputCounter + 1}
});
console.log(this.state.inputCounter);
}
else {
this.setState(prevState => {
return {inputCounter: prevState.inputCounter}
});
alert("Maximum soru sayısına ulaştınız");
}
};
render() {
let surveyOptions = [];
for (let i = 0; i < this.state.inputCounter; i++) {
console.log(this.state.inputCounter);
surveyOptions.push(
<View key={i}>
<View>
<TextInput
style={[styles._surveyTextInput, {borderColor: this.state.inputBorderColor.bar}]}
onChangeText={(text) => this.setState({text})}
value={this.state.text}
onFocus={this.changeInputBorderColor}
onBlur={this.changeInputBorderColor2}
placeholder={"Secenek " + (i + 1)}
/>
</View>
</View>
)
}
return (
<View style={styles._surveyMainContainer}>
<View style={{flex: 0.8}}>
{surveyOptions}
<TouchableOpacity style={{position: 'absolute', right: 5, top: 5}}>
<Ionicons name={"ios-close-circle"}
size={30}
color={'black'}
/>
</TouchableOpacity>
<TouchableOpacity style={{position: 'absolute', right: 5, top: 45}}
onPress={this.incrementInputCount}>
<Ionicons name={"ios-add-circle"}
size={30}
color={'blue'}
/>
</TouchableOpacity>
</View>
<View style={{flex: 0.2}}>
<View
style={styles.renderSeparator}
/>
<Text style={{fontWeight: 'bold', margin: 5}}>Anket süresi</Text>
</View>
</View>
);
}
You can do it with a .map however you have to set it up correctly so that each TextInput has its own value in state. Currently what you are doing is setting the same value in state for each TextInput this results in every TextInput having the same value. Clearly not what you want.
Create an initial array in state (textArray) that has all values as empty strings, this will be used to store the values from each TextInput.
Set the focusedIndex to be null in state
Create a function that uses the previous state value to update the current state.
Create a function to handle the changing of the box color, it will just compare the TextInput index with the current focusedIndex
Iterate over the textArray and create the TextInput components. Make sure each TextInput has its own value in state.
Make sure we set the value of the focusedIndex in the onFocus and onBlur in the TextInput. When it blurs we should set the value to null so that it removes the border color when the keyboard is dismissed.
So we could do something like the following
export default class App extends React.Component {
constructor(props) {
super(props);
// construct an array with the number of textInputs we require,
// each value an empty string
// set this array in state
// set the focusedIndex to null
let textArray = Array(6).fill('');
this.state = {
textArray: textArray,
focusedIndex: null
}
}
// this function will handle setting of the state when each TextInput changes
onChangeText = (text, index) => {
// as there are going to be a lot of setState calls
// we need access the prevState before we set the next state.
this.setState(prevState => {
prevState.textArray[index] = text
return {
textArray: prevState.textArray
}
}, () => console.log(this.state.textArray))
}
// handle the border color
handleBorderColor = (index) => {
return index === this.state.focusedIndex ? 'red' : 'grey'
}
render() {
// here we map the items in the `this.state.textArray`
// notice that each TextInput is give a specific value in state
// that will stop the overlap
return (
<View style={styles.container}>
{this.state.textArray.map((text, index) => {
return <TextInput
style={{height: 40, marginVertical: 10, borderColor: this.handleBorderColor(index), borderWidth: 1}}
onChangeText={text => this.onChangeText(text, index)}
value={this.state.textArray[index]}
placeholder={`placeholder for ${index}`}
onFocus={() => this.setState({focusedIndex: index})}
onBlur={() => this.setState({focusedIndex: null})}
/>
})}
</View>
);
}
}
If you then want to access a specific value for a TextInput you can do so like this
let value = this.state.textArray[index]; // where the index is the value you want
Here is an example snack showing the code working
https://snack.expo.io/#andypandy/map-multiple-textinputs
It is definitely worthwhile looking at the following articles about state, as I have used these properties in this example.
https://medium.learnreact.com/setstate-is-asynchronous-52ead919a3f0
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296
https://medium.learnreact.com/setstate-takes-a-function-56eb940f84b6

react-native: InputText activity firing TouchableHighlight onPress() events

I have the following react-native code where I am trying to build a custom form to gather user input. The form renders correctly with 3 input boxes and a Save button.
In the code below, the issue is that as soon as I start typing on the first TextInput field, the this.saveFormData() which is called only inside the TouchableHighlight button gets fired!
Why are the TextInput events conflicting with the TouchableHighlight ones? How do I fix the issue?
class NewScreen extends React.Component {
constructor(props) {
super(props);
this.state = { songTitle: null, chord: null, strumPattern: null };
}
saveFormData = () => {
console.log(this.state.songTitle);
() => navigate("Home");
};
render() {
const { navigate } = this.props.navigation;
return (
<View style={styles.row_cell_chord_songTitle}>
<Text style={styles.new_input_label}> Song Title </Text>
<TextInput
style={styles.new_input_field}
onChangeText={text => this.setState({ songTitle: text })}
value={this.state.songTitle}
/>
<Text style={styles.new_input_label}> Chords </Text>
<TextInput
style={styles.new_input_field}
onChangeText={text => this.setState({ chord: text })}
value={this.state.chord}
/>
<Text style={styles.new_input_label}> Strumming Pattern </Text>
<TextInput
style={styles.new_input_field}
onChangeText={text => this.setState({ strumPattern: text })}
value={this.state.strumPattern}
/>
</View>
<TouchableHighlight
style={styles.saveButton}
onPress={this.saveFormData()} // <-- typing on above Inputbox fires this function.
>
<Text style={styles.buttonText}>
<FontAwesome>{Icons.heart}</FontAwesome> Save
</Text>
</TouchableHighlight> */
</View>
</View>
</View>
);
}
}
You need to pass a function to onPress, currently you're invoking the function and passing whatever it returns. You just need to change it to:
onPress={this.saveFormData}

React Nat Counter

When clicking on a button, I want a value to increment by one each time. I want to call a function incrementCounter() to handle the operation for me, rather than: onPress={() => this.setState({Count: ++this.state.Count})}
This is my code so far for the class:
class Main extends React.Component{
constructor(props){
super(props);
this.state = {
Count: 0,
}
}
incrementCounter(){
this.setState({
Count: this.state.Count + 1 //!!! Where error occurs
});
}
render() {
return(
<View style={styles.mainContainer}>
<Text style={styles.title}>{'Count: ' + this.state.Count} </Text>
<TouchableHighlight
style={styles.button}
onPress={this.incrementCounter}
underlayColor='white'>
<Text style={styles.buttonText}>+</Text>
</TouchableHighlight>
</View>
)
}
};
The code above results in an error - a red screen:
"undefined is not an object (evaluating 'this.state.Count')" with the error on the line of the incrementCounter() <- Where comments show error occurs.
Most of the examples that I can find online are not using the ES6 syntax, however I want to try and stick to this to keep it standard across my application. The future work will include having a decrementCounter() function that will do the opposite, however will not allow the counter to drop below zero.
I see you are using the es6 notation, try the following:
render() {
return(
<View style={styles.mainContainer}>
<Text style={styles.title}>{'Count: ' + this.state.Count} </Text>
<TouchableHighlight
style={styles.button}
onPress={this.incrementCounter.bind(this)}
underlayColor='white'>
<Text style={styles.buttonText}>+</Text>
</TouchableHighlight>
</View>
)
}
Notice the .bind(this) on the event, this is needed so that it acutally references the class itself not some global function.

Categories