How to pass a list with objects from Server to FlatList? - react-native

I'm trying to create a FlatList that contains an array with objects that has an array from a server and I can't get it to work.
my error message:
TypeError: Cannot read proparty 'Data' of undefined
I can get it to work with my normal list that's not fetched from a Server. Heres the structure from my working list
[{"DATA":[{"filter_id":"44","filter_name":"filter 1"}, {"filter_id":"45","filter_name":"filter 2"},{"filter_id":"46","filter_name":"filter 3"},{"filter_id":"47","filter_name":"filter 4"},{"filter_id":"48","filter_name":"filter 5"}],"MESSAGE":"DATA FOUND","STATUS":200}]
My server list have the same structure but different values of filter_name and filter_id
here's my code:
constructor(props){
super(props);
this.state = {
data: [],
oldData: [{"DATA":[{"filter_id":"44","filter_name":"filter 1"},{"filter_id":"45","filter_name":"filter 2"},{"filter_id":"46","filter_name":"filter 3"},{"filter_id":"47","filter_name":"filter 4"},{"filter_id":"48","filter_name":"filter 5"}],"MESSAGE":"DATA FOUND","STATUS":200}],
page:1,
status: null,
isLoading: false,
}
}
getData = async () => {
const url = 'api/getFilter.php?page='+this.state.page+'&row_per_page=5';
fetch(url).then((response) => response.json())
.then((responseJson) => {
this.setState({
data:this.state.data.concat(responseJson),
isLoading:false
});
})
}
componentDidMount() {
this.getData();
}
renderRow = ({item}) => {
console.log('item', item);
return (
<View style={styles.item}>
<Text>{item.filter_name}</Text>
</View>
)
}
render() {
console.log('state', this.state.data[0]);
console.log('oldstate', this.state.oldData[0]) // this
return (
<View style={styles.container}>
<FlatList
data={this.state.data[0].DATA}
renderItem={this.renderRow}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
Expo: https://snack.expo.io/#thesvarta/tenacious-sandwich

The issue is that on the initial rendering of your component this.state.data is empty, because we have to wait until getData returns any data. That's why you cannot access this.state.data[0].DATA at the beginning.
The solution is to update your getData function a little bit.
getData = async () => {
const url = 'http://ollenorstrom.se/ollenorstrom.se/avoka/api/getFilter.php?page='+this.state.page+'&row_per_page=5';
fetch(url).then((response) => response.json())
.then((responseJson) => {
// here we save the data, we want to access later.
console.log('responseJson', responseJson[0].DATA);
this.setState({
data:this.state.data.concat(responseJson[0].DATA),
isLoading:false
});
})
}
Now your data is directly stored in this.state.data. We now can simplify your render() function:
<FlatList
data={this.state.data} // simplified, passing an empty array at the beginning is ok
renderItem={this.renderRow}
keyExtractor={(item, index) => index.toString()}
/>
Working example:
https://snack.expo.io/HJ--GFlnN

Related

React Native: How to change inputfield to selected item

So what I'm trying to do is fetching data from an API (works well), that has this autocomplete function.
Link to example: https://autocomplete.aws.dk/
Link to the guide: https://autocomplete.aws.dk/guide2.html
What is hard for me, is that the guide is HTML, and this is suppose to work in React Native.
So far I made an input field, that can detect when writing minimum two letters will show a list of addresses.
What I want is when the address is clicked, it takes that value and places it in the input field.
Heres my code:
The API fetch:
import React from "react";
import url from "./url";
export default class DawaFetch extends React.Component {
static defaultProps = {
options: {},
minCharacters: 2,
};
state = {
value: "",
suggestions: [],
};
handleChange = ({ target: { value } }) => {
this.setState({ value });
if (this.props.minCharacters <= value.length) this._fetch(value);
};
_fetch = (value) => {
fetch(
url("https://dawa.aws.dk/adresser/autocomplete", {
q: value,
["per_side"]: 100,
...this.props.options,
}),
{
method: "GET",
headers: {
"Accept-Encoding": "gzip, deflate",
},
}
)
.then((response) => response.json())
.then((json) => this.setState({ suggestions: json }))
.catch((err) => console.error("parsing failed", err));
};
render = () =>
this.props.children({ ...this.state, handleChange: this.handleChange });
}
And here is my view:
<DawaFetch>
{({ value, suggestions, handleChange }) => {
console.log(suggestions);
return (
<View>
<CustomInputs
type="text"
value={value}
onChange={handleChange}
/>
{suggestions.map((suggestion) => (
<TouchableOpacity>
<NormalText key={suggestion.adresse.id}>{suggestion.tekst}</NormalText>
</TouchableOpacity>
))}
</View>
);
}}
</DawaFetch>
Tried different solutions by making it a FlatList with renderItem, and making an onPress function, but I could never make it work.
Hopefully someone can guide me in the right direction, I might been overthinking this.
React-Native TextInput onChange value is not available inside the target as it's available in HTML, Remove target from handleChange function like below, also it's not onChange it's onChangeText in TextInput.
handleChange = (value) => {
this.setState({ value });
if (this.props.minCharacters <= value.length) this._fetch(value);
};
You can achieve your desired functionality in a very simple manner.
Add this to your DawaFetch class.
OnItemSelection=(address)=>{
this.setState({value: address})
}
Add this to your render Function
render = () =>
this.props.children({ ...this.state, handleChange: this.handleChange, OnItemSelection: this.OnItemSelection });
}
Then make these changes in your DawaFetch component
<DawaFetch>
{({ value, suggestions, handleChange, OnItemSelection }) => {
console.log(suggestions);
return (
<View>
<CustomInputs
type="text"
value={value}
onChangeText={handleChange}
/>
{suggestions.map((suggestion) => (
<TouchableOpacity onPress={()=> OnItemSelection(suggestion.adresse)}>
<NormalText key={suggestion.adresse.id}>{suggestion.tekst}</NormalText>
</TouchableOpacity>
))}
</View>
);
}}
</DawaFetch>
Edit:Here is Snack having solution
https://snack.expo.io/#waheed25/bad-raisins

Filter button with react native

I would like to create a button in order to filter some data
I am able to see everything I want to see and I can use the filter on "componentDidMount() setData"
I would use my function on "TouchableOpacity onPress={this.buttonFilterMac}"
Constructor :
constructor(props) {
super(props)
this.state = {
data: []
}
}
My Data :
componentDidMount() {
const url = 'https://next.json-generator.com/api/json/get/V1geuzIDB'
fetch(url)
.then((response) => response.json())
.then((responseJson) => {
this.setState({
data: responseJson.Employe
// data: responseJson.Employe.filter(x => x.Prenom == 'Abrahim')
})
})
.catch((error) => {
console.log('====================================');
console.log(error);
console.log('====================================');
})
}
My Fonction
buttonFilterMac({ item }){
data: responseJson.Employe.filter(x => x.Prenom == 'Abrahim')
}
My button with a image
<TouchableOpacity onPress={this.buttonFilterMac}>
<Image
source={button1}
style={{ width: 160, height: 18 }}
resizeMode="contain"
//style={styles.image_car}
/>
thank a lot for your help
On fetch you need to set the list of data:
this.setState({ data: responseJson.Employe })
When you click filter, you need to set state again with the filtered data in roder to see change in you Component:
buttonFilterMac = () => {
this.setState({ data: this.state.data.filter(x => x.Prenom === 'Abrahim') })
}
How ever, keep in mind when you set your filtered data, the ones not included in the filtered data would be lost. So maybe you could set the original data in a local variable after fetch or in the state. Then you could work with it later.
An example data array for a FlatList:
render() {
const data = this.state.filteredData || this.state.originalData;
return <FlatList data={data} />;
}
With the filter button you can turn on/off the filter, keep in mind to unset this.state.filteredData in order to display this.state.originalData.

Unhandled Promise Rejection : Existing value for key "Favorites" must be of type null or array, revived string

my problem is quite simple but I'm new to react native dev. I'd like to save multiple elements with AsyncStorage (I'm using react-native-simple-store
a library that works like a wrapper but it's same logic) I want display all items for a key in a list , my code look like this:
constructor(props) {
super(props)
this.state = {
UserInput: "",
}
}
SaveValue = () => {
store.push('Favorites', this.state.UserInput)
Keyboard.dismiss()
};
FetchValue = () => {
store.get('Favorites').then((value) => {
this.setState({
favs: value
});
}).done();
};
Same thing with AsynStorage, it just update the item which is not my goal, I'd like to add a new one
SaveValue = () => {
AsyncStorage.setItem("Favorites", this.state.UserInput);
Keyboard.dismiss()
};
FetchValue = () => {
AsyncStorage.getItem("Favorites").then((value) => {
this.setState({
favs: value
});
}).done();
};
This part is my view where I try to display data, you can also see that I use a text input and two buttons one to save and the other to display an array of items stored
render() {
return (
<View>
<TextInput
onChangeText={(UserInput) => this.setState({UserInput})}
placeholder= "Type something"
value={this.state.UserInput} />
<TouchableOpacity
onPress={this.SaveValue}>
<Text>Save</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.FetchValue}>
<Text>Fetch</Text>
</TouchableOpacity>
<Text>{this.state.favs}</Text>
</View>
);
}
At this point I can see only one item, I tried to figure it out and saw that I have to use another method called push but when I changed save by push it throw me an error
Unhandled Promise Rejection : Existing value for key "Favorites" must be of type null or array, revived string.
Thanks!
it will work :)
renderFavorites = () => {
AsyncStorage.getItem("Favorites").then((favs) => {
favs.map((fav) => {
return (<Text> {fav} </Text>);
});
});
}
render() {
return (
<View>
<TextInput
onChangeText={(UserInput) => this.setState({UserInput})}
placeholder= "Type something"
value={this.state.UserInput} />
<TouchableOpacity
onPress={this.SaveValue}>
<Text>Save</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.FetchValue}>
<Text>Fetch</Text>
</TouchableOpacity>
{this.renderFavorites()}
</View>
);
}
Solution using JSON:
SaveValue = () => {
const newFavs = [...this.state.favs, this.state.UserInput];
this.setState({ favs: newFavs, UserInput: '' }, () => {
AsyncStorage.setItem("Favorites", JSON.stringify(this.state.favs));
Keyboard.dismiss()
});
};
FetchValue = () => {
AsyncStorage.getItem("Favorites").then((value) => {
this.setState({
favs: JSON.parse(value)
});
}).done();
};

How to make react-native-banner-carousel load images from API(asynchronous)

I'm using https://github.com/f111fei/react-native-banner-carousel/
It works fined with hardcoded images path.
But this error happened if my images array is empty. It will show error as this image
I guess it caused by empty array (please correct me if im wrong). The state.carousels yet to loading to state when it render.
How can I make it asynchronous, so it can load the images dynamically.
So this is my code.
Dashboard.js
componentWillMount(){
this.props.carouselFetch();
}
renderPage(image, index) {
return (
<View key={index}>
<ImageFluid
source={{ uri: image }}
originalWidth={ 2500 }
originalHeight= { 1000 }
/>
</View>
);
}
render(){
const images = this.props.carousels;
return(
......
<Carousel
autoplay
autoplayTimeout={5000}
loop
index={0}
showsPageIndicator={ false }
pageSize={BannerWidth}
>
{ images.map((image, index) => this.renderPage(image, index))}
</Carousel>
......
);
}
const mapStateToProps = (state) => {
const carousels = state.carousel;
return { carousels };
};
CarouselActions.js
export const carouselFetch = () => {
return (dispatch) => {
fetch('API json')
.then((response) => response.json())
.then((response) => {
if (response.Status === 'Fail') {
return Promise.reject(response)
}
return response
})
.then(carousels => {
carouselFetchSuccess(dispatch, carousels);
})
.catch(() => console.log("Error"));
};
};
const carouselFetchSuccess = (dispatch, carousels) => {
dispatch({
type: CAROUSEL_FETCH_SUCCESS,
payload: _.map(carousels.data, i => i.image_path)
});
};
My Sample API json
The package required sample array method
render(){
const images = this.props.carousels;
if (!images || images.length === 0) {
return null;
}
return(
......
<Carousel
autoplay
autoplayTimeout={5000}
loop
index={0}
showsPageIndicator={ false }
pageSize={BannerWidth}
>
{ images.map((image, index) => this.renderPage(image, index))}
</Carousel>
......
);
}
Don't render carousel when the image list length is 0.
First use :
const images = ( this.props.carousels || [] ).map( (image) => ( {
value: carousels .name,
label: carousels .id,
order: carousels .data.
} );
You have a simple array of objects not an nested array, your response it's a simple res.data and not a res.carousel.data, if you use console.log(res) you will see your array, check that.

How to call fetch() inside renderItem on FlatList?

I have been unable to solve this so far, I hope someone from here can help me :)
The situation is as following:
I load a list of users from a department using an API which returns it as JSON (this works).
For each user in the JSON file, I have to fetch another JSON file containing sensor data (this does not work).
I am in fact capable of getting the JSON data from the getStatus() function, but it is not correct. Because, when the FlatList is rendered, the data still has not been fetched, but on refresh, calling fetchUsers(), the sensor data shows up, but it shows the same sensor data on all users and not their own specific sensor data.
At the moment I have made it spit out what ever JSON it receives in a Text element in order to see what was returned...
I am not allowed to upload pictures here, but I have made an album on Imgur which contains images of the application to help explain the issue: https://imgur.com/a/5XLpR
export default class SensorData extends Component {
constructor(props) {
super(props);
this.stats = null;
this.state = {
isLoading: true,
error: false,
userDataSource: [],
refreshing: false,
time: 30,
navigation: props.navigation,
};
}
componentDidMount() {
this.fetchUsers();
}
fetchUsers(){
return fetch('http://url-to-the-json/json/patients.json')
.then((response) => response.json())
.then((response) => {
this.setState({
isLoading: false,
error: false,
userDataSource: response,
refreshing: false,
time: 30,
}, function () {
});
})
.catch((error) => {
this.setState({
isLoading: false,
error: true
})
});
}
getStatus(patientId) {
fetch("http://url-to-the-json/json/status/" + patientId + ".json")
.then((response) => response.json())
.then((responseJson) => {
this.stats = responseJson;
})
.catch((error) => {
Alert.alert("Error");
});
return this.stats;
};
renderItem(item, index) {
var x = index % 2 === 0;
status = this.getStatus(item.patientId);
return (
<View style={x ? styles.rowEven : styles.rowOdd}>
<TouchableOpacity style={styles.rowName} onPress={this.handlePress.bind(this, item)}>
<Text style={styles.rowNameText}>{item.firstname} {item.lastname}</Text>
</TouchableOpacity>
<Text>{JSON.stringify(status)}</Text>
</View>
)
}
render() {
return (
<View>
<View style={styles.reloadTimer}>
<Text style={styles.timerText}>Reloading in {this.state.time} seconds.</Text>
</View>
<View style={styles.listView}>
<FlatList
data={this.state.userDataSource}
renderItem={({ item, index }) => this.renderItem(item, index)}
keyExtractor={item => item.patientId}
refreshing={this.state.refreshing}
onRefresh={this.handleRefresh}
ItemSeparatorComponent={this.renderSeparator}
/>
</View>
</View>
);
}
}
The variable status is the one in question.
I hope I formulated the question in an understandable manner.
The users JSON file looks like this:
{
"patientId": "ec276ca9-f9ab-429b-b34e-23fcf448d714",
"firstname": "Julie",
"lastname": "Nielsen",
"birthDate": "1930-01-01T00:00:00Z",
"departmentId": "709f59ae-67fe-447c-bed3-7b5912703861",
"patientNumber": null,
"entryDate": null,
"dischargeDate": null,
"editedOn": null,
"editedBy": null
}
The sensor data JSON file looks like this:
{
"status": {
"in_bed": false,
"in_room": true,
"clean_diaper": true
}
}
You need to store the result of getStatus in component state like you do with the patients JSON. Assuming you have a "stats" object in your state that maps a patient to their stats, you can do:
getStatus(patientId) {
fetch("http://url-to-the-json/json/status/" + patientId + ".json")
.then((response) => response.json())
.then((responseJson) => {
let stats = Object.assign({}, this.state.stats);
stats[patientId] = responseJson;
this.setState({ stats: stats });
})
.catch((error) => {
Alert.alert("Error");
});
};
Then, in the renderItem function, you can render the stats from state, or render a placeholder text if the stats haven't been loaded yet.
Also, you should not make network requests inside a render function, since they can be called quite often and multiple times for the same component. Instead, you should look into the FlatList API and the callbacks for changes in visibility.
Hope this helps...