I am navigating to this 'History' tab from a side menu in React Native Navigation. Got a username for which I get all the 'bookings' made, but I can see in the warning tab that there are countless requests being made even after the component has been mounted, so there's an infinite loop probably caused by setState. Where should I call getHistory(), as in to make only one request, unless of course the component is reloaded. Thank you!
constructor(props){
super(props);
this.state = {
loggedUser: 'none',
bookingsInfo: []
}
}
getData = async () => {
try {
const value = await AsyncStorage.getItem('loggedUser')
if(value !== null) {
this.setState({
loggedUser: value
})
}
} catch(e) {
console.error(e);
}
}
getHistory() {
fetch('https://porsche.e-twow.uk/reactnative/istoric.php', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'Cache-Control': 'no-cache, no-store'
},
body: JSON.stringify({
username: this.state.loggedUser
})
})
.then((response) => response.json())
.then((data) => {
this.setState({
bookingsInfo: data
});
})
.catch((error) => {
console.error(error);
});
}
componentDidMount() {
this.getData();
}
render() {
this.getHistory();
return (
<View style={styles.view}>
<ScrollView style={styles.scrollView}>
{
this.getHistory()
}
{
this.state.bookingsInfo ? this.state.bookingsInfo.map((item, index) => {
return (
<View style={styles.mainButton} key={item.id_scooter}>
<Text style={styles.mainButtonText}>Scooter-ul {item.id_scooter}</Text>
<Text style={styles.mainButtonText}>Data start: {item.start}</Text>
<Text style={styles.mainButtonText}>Data final: {item.end}</Text>
</View>
)
}) : null
}
</ScrollView>
<Footer/>
</View>
);
}
}
you are setting state in render.Calling setState here makes your component a contender for producing infinite loops.
place getHistory in componentDidMount .
componentDidMount() {
this.getHistory();
}
Related
I didn't understand how to make the AsyncStorage work.
I use react-native-router-flux
Basically I have 3 pages:
FirstPage
export default class Authentication extends Component {
render() {
return (
..........
<TouchableOpacity
style ={[style.button, style.buttonOK]}
onPress={() => Actions.login() }>
<Text style={style.buttonTesto}>LOGIN</Text>
</TouchableOpacity>
<TouchableOpacity
style ={[style.button, style.buttonOK]}
onPress={() => Actions.signup() }>
<Text style={style.buttonTesto}>SIGNUP</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
Login
login() {
let ctrl = true;
......
if (ctrl) {
let formdata = new FormData();
const identity = {
AppName: {
Username: this.state.username,
Password: this.state.password
}
};
formdata.append("Identity", JSON.stringify(identity));
fetch(APILINK , {
method: "POST",
headers: {
"Content-Type": "multipart/form-data"
},
body: formdata
})
.then(response => response.json())
.then(responseData => {
if (responseData.Error) {
.......
} else {
global.utente = new Utente(responseData);
Actions.homepageutente();
}
})
.catch(err => alert("err:" + err));
}
}
Utente
export default class Utente {
constructor(data) {
Utente.saveUtenteLoggato(data);
this._data = data;
....
);
}
get data() {
return this._data;
}
//there is a function for the signup there//
.......
static async saveUtenteLoggato(value) {
try {
await AsyncStorage.setItem("#UtenteLoggato", JSON.stringify(value));
} catch (error) {
console.log(error.message);
}
}
static async getUtenteLoggato() {
try {
return await AsyncStorage.getItem("#UtenteLoggato");
} catch (error) {
console.log(error.message);
return null;
}
}
static async clearUtenteLoggato() {
try {
global.utente = null;
await AsyncStorage.removeItem("#UtenteLoggato");
} catch (error) {
console.log(error.message);
return null;
}
}
}
So in Utente I have created the Asyncstorage function, but I don't understand how I should do when I close the app in backgroun (for example) to maintain the login active. At the moment if I go back in the App I should do another time the Login.
How can I solve it?
EDIT
Starting page
class Starting extends Component {
constructor(props)
{
super(props)
this.state = {
loading: true
}
}
componentWillMount() {
Utente.getUtenteLoggato()
.then(dataUtenteLoggato => {
if (dataUtenteLoggato !== null) {
global.utente = new Utente(JSON.parse(dataUtenteLoggato));
} else {
Actions.authentication();
}
})
.catch(err => {
console.log(err);
})
.finally(() => {
this.setState({ loading: false });
});
}
render() {
return(
<View style={style.container}>
<Spinner visible={this.state.loading} textContent={"Loading..."} textStyle={{color: '#FFF'}} />
</View>
);
}
}
You can implement splash screen component and check auth in componentWillMount. As example - get data from AsyncStorage, then perform request to check that user is authenticated and fetch user details. If auth data(e.g. auth token) is absent in storage or server threw auth error(in case when token is invalid or expired), redirect user to login screen, else mark user as authenticated and show main screen.
I'am trying to Update and Delete some items by ID upon tap/click of Update And Remove buttons. But my problems is, I'm not sure what is the proper approach doing a "fetch with a state(or anything) for the ID's".
With these approach, I have errors. (Ex: undefined is not an object ...)
Without these, I have nothing. I'am well aware of that because I'm not referring to anything. So I have to use ID. But I don't know the approach.
Please help, anything will be appreciated. :)
Here is my code
Settlement.js
export default class Settlement extends Component {
constructor(props){
super(props)
this.state = {
...
deleteOrder: '',
numOrder: '',
};
}
submit = () => {
this.state.numOrder = item.order_id; // here's my fetch with state
fetch('http://192.168.254.***:****/SendOrder/update' + this.state.numOrder, {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
spcl_req: this.state.SplOrder,
order_quantity: this.state.count,
order_type: this.state.DineIn,
})
}).then(res => res.json())
.then((responseJson) => {
Alert.alert(JSON.stringify(responseJson))
console.log(responseJson);
})
.catch(error => console.error('Error:', error))
}
delete = () => {
this.state.deleteOrder = item.order_id; // here's my fetch with state
fetch('http://192.168.254.***:****/SendOrder/delete_order/' + this.state.deleteOrder, {
method: 'DELETE',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
order_id: this.state.deleteOrder
})
}).then((responseData) => {
Alert.alert(JSON.stringify(responseData))
console.log(responseData.rows)
}).done();
}
render() {
const { params } = this.props.navigation.state;
return (
<View>
<FlatList
...
renderItem = {({ item }) =>
<View>
....
<View>
<TouchableOpacity
onPress = { () => this.submit() }>
<Text>UPDATE</Text>
</TouchableOpacity>
<TouchableOpacity
onPress = { () => this.delete() }>
<Text>REMOVE</Text>
</TouchableOpacity>
</View>
</View>
}/>
</View>
)
}
}[![enter image description here][1]][1]
You broke a couple of concepts in React.
this.state.numOrder = item.order_id;
This line will not update your state
What's in the item in this line? renderItem = {({ item }) =>
Perhaps in this object there is an identifier, which must be passed as an argument to your functions
part of this app I'm working on for class is supposed to be scanning the barcode of a book (using the expo XDE barcodescanner component) and then sending the scanned barcode to a database that another group in my class is handling. My issue right now is that every time I do a scan, I see in my console that I'm sending multiple duplicate PUT requests. I think the problem is that the expo barcodescanner doesn't just scan once and then stop, but keeps on scanning, and each time it scans, my state is "updated" and component is re-rendered. Does anyone have any suggestions on how I can modify my code to make sure that I'm not re-rendering over and over again with the same data? I've included the relevant code below. Note: some of the data is hard-coded for testing purposes. Thank you!
class SecondScreen extends Component {
constructor(props) {
super(props);
this.state= {
results: []
}
this.fetchData = this.fetchData.bind(this);
}
fetchData(URL) {
return fetch(URL)
.then((response) => response.json())
.then((responseData) => {
return responseData
})
.catch((error) => {
console.error(error)
})
}
_handleBarCodeRead = data => {
let isbn = data.data;
let URL = 'https://www.googleapis.com/books/v1/volumes?q=isbn:' +
isbn;
this.fetchData(URL).then(bookResult => {
this.setState({ results: bookResult }
fetch('https://p0kvnd5htd.execute-api.us-east-
2.amazonaws.com/test/return', {
method: 'PUT',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
'libraryName': 'lib1', //libName
'bookBarcode': '18263' //isbn
}),
})
.then((response) => {
if (response.status == 400) {
console.log(response.status)
alert('Return unsuccessful, please try again.');
}
else {
console.log(response.status)
this.props.navigation.navigate('ThirdPage', { title:
this.state.results.items[0].volumeInfo.title });
}
})
})
}
render() {
return (
<BarCodeScanner
onBarCodeRead={this._handleBarCodeRead}
style={[StyleSheet.absoluteFill, styles.container]}
>
<View style={styles.layerTop} />
<View style={styles.layerCenter}>
<View style={styles.layerLeft} />
<View style={styles.focused} />
<View style={styles.layerRight} />
</View>
<View style={styles.layerBottom} />
</BarCodeScanner>
);
}
}
Easy fix would be to use lodash's debounce function:
command line: npm i lodash
top of javascript:
import _ from 'lodash'
wrap _handleBarCodeRead in debounce, this will prevent _handleBarCodeRead from being called multiple times for 3 seconds after the last call:
_debouncedHandleBarCodeRead = _.debounce((data) =>{ this._handleBarCodeRead(data) }, 3000, {leading: true, trailing: false});
change BarCodeScanner to used the debounced method:
<BarCodeScanner onBarCodeRead={this._debouncedHandleBarCodeRead} >
How to make load more with FlatList of React Native (Not infinite)
I've done this, but unfortunately it loads as infinitely.
This is my code snippet
<FlatList
data={this.props.data}
renderItem={({ item, separators }) => (
<TouchableHighlight
onPress={() => this._onPress(item)}
onShowUnderlay={separators.highlight}
onHideUnderlay={separators.unhighlight}
>
<Text> {item.title} </Text>
</TouchableHighlight>
)}
keyExtractor={item => item.id}
ListFooterComponent={this.renderFooter}
onEndReached={this.props.handleLoadMore}
onEndThreshold={0}
/>
And my handleLoadMore
handleLoadMore = () => {
console.log("test"); // <---- this line run infinitely
fetch(url, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(filters)
})
.then(response => response.json())
.then(responseJson => {
this.setState({
itemData: [
...this.state.itemData,
...responseJson.estate_list
],
itemPage: this.state.itemPage + 1
});
})
.catch(error => {
console.error(error);
});
};
There is issue when loading data in FlatList and your onEndReached handler will be called when the view is re-rendered. Try setting a flag like this :
constructor(props){
super(props);
this.state = {
hasScrolled: false
}
}
Then add this method :
onScroll = () => {
this.setState({hasScrolled: true})
}
Hook it up to FlatList:
<FlatList
onScroll={this.onScroll}
Finally load only when scrolled :
handleLoadMore = () => {
if(!this.state.hasScrolled){ return null; }
//here load data from your backend
}
constructor(props){
super(props);
this.state = {
loading: true
}
}
<FlatList
onEndReached={this.handleLoadMore}/>
handleLoadMore = () => {
if(!this.state.loading){ return null; }
this.setState({
page: this.state.page + 1,
loading: false
}, () => {
this.loadProducts();
});
};
loadProducts(catId,userkey){
$this.setState({
loading:true
});
}
I'm using a material dropdown in my application
<Dropdown
baseColor='white'
itemColor='white'
label='Select Cluster'
/>
I fetch JSON object like this and it works fine.
fetch('url', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
username : "admin"
})
}).then((response) => response.json())
.then((responseJson) => {
var count = Object.keys(responseJson.message.Obj).length;
for(var i=0;i<count;i++){
console.log(responseJson.message.Obj[i].name) // I need to add
//these names to dropdown
}
})
.catch((error) => {
console.error(error);
});
Now I need to add the responseJson.message.Obj[i].name values to
my dropdown list.
Supposing that you're using react-native-material-dropdown.
Example:
Dropdown component should be rendered as follows:
<Dropdown
baseColor='white'
itemColor='white'
label='Select Cluster'
data={this.state.drop_down_data} // initialise it to []
/>
Request code:
fetch('url', {
...
}).then((response) => response.json())
.then((responseJson) => {
var count = Object.keys(responseJson.message.Obj).length;
let drop_down_data = [];
for(var i=0;i<count;i++){
console.log(responseJson.message.Obj[i].name) // I need to add
drop_down_data.push({ value: responseJson.message.Obj[i].name }); // Create your array of data
}
this.setState({ drop_down_data }); // Set the new state
})
.catch((error) => {
console.error(error);
});
Doc:
React native state management
react-native-material-dropdown
You can achieve this by using react native "state". Create a state then assign it to Dropdown component's data property. Then set responseJson.message.Obj[i].names to the state by using "this.setState()" method.
Find out what is the shape of the data property needed (i.e. Array of Objects) for the <Dropdown /> component you are using
Make fetch calls inside componentDidMount
Treat the state of your component as if it were immutable (do not push directly to this.state. dropdownData)
Here is some sample code using react-native-material-dropdown:
class Example extends React.Component {
// Use constructor to assign initial state
constructor(props) {
this.state = {
dropdownData: []
};
}
componentDidMount() {
fetch('url', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
username : "admin"
})
})
.then((response) => response.json())
.then((responseJson) => {
var count = Object.keys(responseJson.message.Obj).length;
// We need to treat this.state as if it were immutable.
// So first create the data array (tempDataArray)
var tempDataArray = [];
for(var i=0;i<count;i++){
// Note: react-native-material-dropdown takes an Array of Objects
tempDataArray.push({
value: responseJson.message.Obj[i].name
});
}
// Now we modify our dropdownData array from state
this.setState({
dropdownData: tempDataArray
});
})
.catch((error) => {
console.error(error);
});
}
// ...
render() {
return (
// ...
<Dropdown
baseColor='white'
itemColor='white'
label='Select Cluster'
data={this.state.dropdownData} // Use dropdownData for the data prop
/>
// ...
);
}
// ...
}
See: AJAX Requests in React
See: react-native-material-dropdown expected data type
Sample code
import React, { Component } from 'react';
import { AppRegistry, StyleSheet, View, Platform, Picker, ActivityIndicator, Button, Alert} from 'react-native';
export default class AddInventory extends Component {
constructor(props)
{
super(props);
this.state = {
isLoading: true,
PickerValueHolder : ''
}
}
componentDidMount() {
return fetch('https://reactnativecode.000webhostapp.com/FruitsList.php')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataSource: responseJson
}, function() {
// In this block you can do something with new state.
});
})
.catch((error) => {
console.error(error);
});
}
GetPickerSelectedItemValue=()=>{
Alert.alert(this.state.PickerValueHolder);
}
render() {
if (this.state.isLoading) {
return (
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
);
}
return (
<View style={styles.MainContainer}>
<Picker
selectedValue={this.state.PickerValueHolder}
onValueChange={(itemValue, itemIndex) => this.setState({PickerValueHolder: itemValue})} >
{ this.state.dataSource.map((item, key)=>(
<Picker.Item label={item.fruit_name} value={item.fruit_name} key={key} />)
)}
</Picker>
<Button title="Click Here To Get Picker Selected Item Value" onPress={ this.GetPickerSelectedItemValue } />
</View>
);
}
}
const styles = StyleSheet.create({
MainContainer :{
justifyContent: 'center',
flex:1,
margin: 10
}
});
You could also take an approach like this:
var Somedata = {
"data" : [
{
"baseColor": "white",
"moreData":[
{
"id" : 118,
}
]
},
{
"baseColor": "grey",
"moreData": [
{
"id": 1231231,
}
]
}
]
}
const renderData = someData.data.map((data) => {
return data.moreData.map(brand => {
strColor = data.baseColor;
console.log("Individual Data :" + strColor)
return `${data.baseColor}, ${moreData.id}`
//setState here?
}).join("\n")
}).join("\n")
//setState here?
You now have a few options you could set the state. From your example, you would set the state the state of your list to the data returned from the fetch call once it has been rendered. A quick thing to keep in mind is that react doesn't support asynchronous loading currently. Therefore the data must be rendered as empty, or with some sample data and then updated once the call has been made to whatever you wish to update! Hope this helps a little :)
https://reactjs.org/docs/faq-ajax.html