Can't get Flatlist pull to refresh working - javascript

The docs are pretty straight forward but somehow I can not get the pull to refresh working. The data is loaded correctly at the componentDidMount but _refreshis not called when I try to pull down the list. I tried it on a iPhone and Android device. On Android I can't even pull down the list (no rubber effect).
Here is my code:
export default class HomeScreen extends Component {
static navigationOptions = { header: null };
state = { data: [], isLoading: true };
_fetchData = async () => {
const data = [];
try {
const response = await fetch('https://randomuser.me/api/?results=10');
const responseJSON = await response.json();
this.setState({ data: responseJSON.results, isLoading: false });
} catch (error) {
alert('some error');
this.setState({ isLoading: false });
}
};
_refresh = () => {
alert('this is never be shown');
this.setState({ isLoading: true });
this._fetchData();
};
componentDidMount() {
this._fetchData();
}
render() {
if (this.state.isLoading)
return (
<View style={styles.container}>
<ActivityIndicator size="large" color="darkorange" />
</View>
);
return (
<View style={styles.container}>
<FlatList
data={this.state.data}
keyExtractor={item => item.email}
renderItem={({ item }) => (
<FriendListItem
friend={item}
onPress={() =>
this.props.navigation.navigate('FriendsScreen', {
friend: item,
})
}
/>
)}
ItemSeparatorComponent={() => <View style={styles.listSeparator} />}
ListEmptyComponent={() => <Text>empty</Text>}
onRefresh={this._refresh}
refreshing={this.state.isLoading}
/>
</View>
);
}
}

Double check your FlatList import. I'm pretty sure that you imported FlatList from react-native-gesture-handler. If yes then remove it.
FlatList should be imported from react-native like below.
import { FlatList } from 'react-native';
If above is not the case then share with me your StyleSheet.
Let me know if it helps.

Related

Connecting REST API in React Native

I am trying to learn how to connect APIs in React Native. I am using a sample API: https://reactnative.dev/movies.json
This is my code:
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
dataSource: [],
};
}
componentDidMount() {
return fetch("https://reactnative.dev/movies.json")
.then((response) => response.json())
.then((responseJson) => {
this.setState({
loading: false,
dataSource: responseJson.movies,
});
})
.catch((error) => console.log(error)); //to catch the errors if any
}
render() {
if (this.state.isLoading) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" color="#0c9" />
</View>
);
} else {
let products = this.state.dataSource.map((val, key) => {
return (
<View key={key} style={styles.item}>
<Text>{val}</Text>
</View>
);
});
return (
<View style={styles.container}>
<Text>{products.title}</Text>
</View>
);
}
}
}
The problem occurs with my "products" variable. In debug mode, I was able to see the key and value pairs which were correct from the API. However, the products array is populated with objects rather than strings which are structured like this:
Object {$$typeof: Symbol(react.element), type: "RCTView", key: "0", …}
My code returns the following error: this.state.dataSource.map is not a function
EDIT:
The answer below worked for the API I was using. Now I am trying a different API structured like this:
{"prods":
{
"86400":{"slug":"86400","url":"/86400"},
"23andme":{"slug":"23andme","url":"/23andme"}
}}
I am having trouble with the mapping again. This returns an error:
return dataSource.map((val, key) => (
<View key={key} style={styles.item}>
<Text>{val.slug}</Text>
</View>
));
First, there is a small typo in your example. In your component's constructor you specify a loading state variable, but in your render function you're using isLoading. Second, you're not mapping over your data correctly. It just looks like you need to specify what aspects of each movie you care about in your render function. JSX can't handle displaying a full javascript object which is what <Text>{val}</Text> ends up being in your code. There are a few ways you can fix this. It's very common to just map over your results and display them directly.
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
dataSource: []
};
}
componentDidMount() {
return fetch("https://reactnative.dev/movies.json")
.then(response => response.json())
.then(responseJson => {
this.setState({
loading: false,
dataSource: responseJson.movies
});
})
.catch(error => console.log(error));
}
render() {
const { loading, dataSource } = this.state;
if (loading) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" color="#0c9" />
</View>
);
}
return dataSource.map((movie, index) => (
<View key={movie.id} style={styles.item}>
<Text>{movie.title}</Text>
</View>
));
}
}
You could also pull this out to a renderMovies method, which might help since you are trying to display these in a styled container.
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
dataSource: []
};
}
componentDidMount() {
return fetch("https://reactnative.dev/movies.json")
.then(response => response.json())
.then(responseJson => {
this.setState({
loading: false,
dataSource: responseJson.movies
});
})
.catch(error => console.log(error));
}
renderMovies() {
const { dataSource } = this.state;
return dataSource.map((movie, index) => (
<View key={movie.id} style={styles.item}>
<Text>{movie.title}</Text>
</View>
));
}
render() {
const { loading } = this.state;
if (loading) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" color="#0c9" />
</View>
);
}
return (
<View style={styles.container}>
{this.renderMovies()}
</View>
);
}
}
I have used Object.values() to restructure the object into an array
componentDidMount() {
return fetch("https://reactnative.dev/movies.json")
.then((response) => response.json())
.then((responseJson) => {
this.setState({
loading: false,
dataSource: Object.values(responseJson.movies), //changed this
});
})
.catch((error) => console.log(error));
}
Try simple way. This code uses modern React practice and helps you to brush up your React skills in general. Give a try.
import React, {useState, useEffect} from 'react';
import { Text, View, StyleSheet } from 'react-native';
import axios from 'axios'; //for fetching data
export default function App() {
//React Hook for state
const [ data, setData ] = useState ([]);
//React Hook Instead Of ComponentDidMount
useEffect(() => {
const fetchData = async () => {
const result = await axios(
"https://reactnative.dev/movies.json",
);
setData(result.data.movies);
};
fetchData();
}, []);
return (
<View>
<Text>{JSON.stringify(data)}</Text>
</View>
);
}

How to prevent re-rendering/fetching data in React Class Component?

I'm working in a music app using React Native, In the Home Screen I make a class component contains more than four FlatList and it's Get data from API "it's large data",
So i make a function For that, and put it inside componentDidMount(),
But I notice when I log the data after setState I see it twice Or more in RN-Debugger
So how can i prevent this happen?
because it's Affected in performance :)
here's a snippet of my code
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
url: '******',
loading: false,
minimal: false,
MiniURL: '',
songName: '',
currentTrackIndex: 0,
isPlaying: true,
};
}
getRecentSongs = async () => {
try {
let response = await API.get('/index');
let {recent_tracks} = response.data.data;
let recent_tunes = [];
recent_tracks.map(track =>
recent_tunes.push({
id: track.id,
name: track.name,
url: this.state.url + track.sounds,
img: this.state.url + track.avatar,
}),
);
let data = response.data.data;
this.setState({data, recent_tunes, loading: true}, () =>
console.log('data', this.state.data),
);
} catch (error) {
console.log(error);
this.setState({error: true});
}
};
componentDidMount() {
this.getRecentSongs();
}
_renderItem = ({item, index}) => {
const {url} = this.state;
return (
<TouchableNativeFeed
key={item.id}
onPress={() => {
this.props.navigation.navigate({
key: 'Player',
routeName: 'Player',
params: {
tunes: this.state.recent_tunes,
currentTrackIndex: index,
},
});
}}
background={TouchableNativeFeedback.Ripple('white')}
delayPressIn={0}
useForeground>
<Card style={styles.card} noShadow={true}>
<FastImage
style={{width: 200, height: 200}}
source={{uri: url + item.avatar}}
resizeMode={FastImage.resizeMode.cover}
style={styles.cardImg}
/>
<Body style={styles.cardItem}>
<View style={styles.radioCardName}>
<View style={styles.cardViewFlex}>
<Text style={styles.text}>{item.name}</Text>
</View>
</View>
</Body>
</Card>
</TouchableNativeFeed>
);
};
render(){
const {data} = this.state;
return(
...
{/* Recent Songs Here*/}
<View style={{marginVertical: 10}}>
<FlatList
horizontal={true}
showsHorizontalScrollIndicator={false}
data={data.recent_tracks}
contentContainerStyle={{flexGrow: 1}}
ListEmptyComponent={<EmptyList />}
keyExtractor={(track, index) => track.id.toString()}
// initialNumToRender={10}
renderItem={this._renderItem}
/>
</View>
...
)
}
}
It's hard to tell from what's been posted, but is it possible that a key on one of the components is changing more often than you're expecting? React will trigger a full re-render if it detects any key changes.
ComponentDidMount will only be executed once and unmounted when it gets deleted. So that means that it is been created twice in some part of your application.
I have encountered a similar problem and it was regarding my navigation library, in my case, I was using react-navigation https://github.com/react-navigation/react-navigation/issues/2599.
So I can suggest checking if something has happened when your component is created and if it is doing it twice. Also, give a double check if your navigation is not doing the same.
Please use React.memo
It will not re-render the component without any relevent data in its props.
eg:
/**Your render item*/
const AddsItem = React.memo(({item, index}) => {
return (
<TouchableNativeFeed
...
...
</TouchableNativeFeed>
);
});
/**Your class*/
class Home extends React.Component {
constructor(props) {
...
}
render(){
const {data} = this.state;
return(
...
...
...
)
}

i want to re create Flatlist every time in react native

Please check shared video form understanding my issue
https://drive.google.com/file/d/1GKU07Mv7IjiLnrfps5gWpfMPsMphvRDv/view
I need to Flatlist screen every time empty because this below code every time Flatlist API called and then after change Flatlist data.
App Flow
first screen: shows category
second screen: shows selected category quotes
import { StyleSheet,Button,View, Text,FlatList,Dimensions,TouchableHighlight,Clipboard,ToastAndroid,Share,YellowBox } from 'react-native';
import { createAppContainer,NavigationEvents } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
import { createDrawerNavigator } from 'react-navigation-drawer';
import { createSwitchNavigator } from 'react-navigation-switch-transitioner'
import Icon from 'react-native-vector-icons/Ionicons';
import Style from '../constants/Style';
import Spinner from 'react-native-loading-spinner-overlay';
export default class QuoteActivity extends React.Component {
static navigationOptions = ({ navigation }) => {
return {
title: navigation.getParam('Title', 'Default Title'),
headerStyle: {
backgroundColor: navigation.getParam('BackgroundColor', '#5F4B8BFF'),
},
headerTintColor: navigation.getParam('HeaderTintColor', '#fff'),
headerLeft: (
<Icon
style={{ paddingLeft: 15,paddingTop: 5,color:'#FFFFFF' }}
onPress={() => navigation.goBack(null)}
name="ios-arrow-back"
size={40}
/>
)
};
};
constructor(props) {
super(props);
YellowBox.ignoreWarnings([
'Warning: componentWillMount is deprecated',
'Warning: componentWillReceiveProps is deprecated',
]);
}
state = {
spinner: false,isLoading: false,
dataSource:[]
}
// ON FOCUS CALL API
componentDidMount() {
this._doApiCall();
}
// API CALL
_doApiCall = () => {
this.setState({
spinner: true,isLoading:true
});
const { navigation } = this.props;
let CategoryId = navigation.getParam('CategoryId');
fetch('API_URL', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
}
}).then((response) => response.json())
.then((responseJson) => {
this.setState({
// SAMPLE JSON
// {
// "data": [
// {
// "Id": "462",
// "CategoryId": "5",
// "Title": "Being 'Single' is My Attitude!"
// },
// {
// "Id": "464",
// "CategoryId": "5",
// "Title": "I never dreamed about success. I worked for it."
// }
// ],
// "success": "1",
// "message": "2 Quotes found.",
// "service_time": "0.030284881591797 seconds"
// }
// SAMPLE JSON END
spinner:false,isLoading:false,
dataSource:responseJson.data
})
})
.catch((error) => {
console.error(error);
});
};
// Copy to clipboard
writeToClipboard = async (text) => {
await Clipboard.setString(text);
ToastAndroid.show('Quote copied!', ToastAndroid.SHORT);
};
// Share quotes
shareQuotes = async (text) => {
const result = await Share.share({
message:text.toString()+'\n\n Download app from play store',
});
};
_keyExtractor = (item) => item.id;
// RENDER DATA ITEMS
_renderItem = ({item}) => {
return (
<View style={Style.CategoryTitleList}>
<Text style={Style.CategoryTitle}>{item.Title}</Text>
<View style={[{ flexDirection:'row',justifyContent: 'center',alignItems:'center',textAlign:'center',alignSelf:"center"}]}>
<Icon name="ios-copy" onPress={() => this.writeToClipboard(item.Title)} title="Copy" size={25} color="#5F4B8BFF" style={[{margin:5,alignItems:'flex-end',paddingTop:3,paddingRight:20,paddingLeft:20 }]} />
<Icon name="ios-share" onPress={() => this.shareQuotes(item.Title)} size={25} color="#5F4B8BFF" style={[{margin:5,alignItems:'flex-end',paddingTop:3,paddingRight:20,paddingLeft:20 }]} />
</View>
</View>
)
}
render() {
// RENDER DATA ITEMS INSIDE RENDER FNS
renderItem = ({ item, index }) => {
return (
<View style={Style.CategoryTitleList}>
<Text style={Style.CategoryTitle}>{item.Title}</Text>
<View style={[{ flexDirection:'row',justifyContent: 'center',alignItems:'center',textAlign:'center',alignSelf:"center"}]}>
<Icon name="ios-copy" onPress={() => this.writeToClipboard(item.Title)} title="Copy" size={25} color="#5F4B8BFF" style={[{margin:5,alignItems:'flex-end',paddingTop:3,paddingRight:20,paddingLeft:20 }]} />
<Icon name="ios-share" onPress={() => this.shareQuotes(item.Title)} size={25} color="#5F4B8BFF" style={[{margin:5,alignItems:'flex-end',paddingTop:3,paddingRight:20,paddingLeft:20 }]} />
</View>
</View>
);
};
return (
<View style={Style.QuotesListView}>
<NavigationEvents
onDidFocus={this._doApiCall}
onWillFocus={() => this.setState({spinner: true})}
/>
<FlatList
data={this.state.dataSource}
renderItem={renderItem} // USED INSIDE RENDER FNS
refreshing={this.state.isLoading}
onRefresh={this._doApiCall}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={1000}
initialNumToRender={1}
removeClippedSubviews={false}
keyExtractor={this._keyExtractor}
/>
</View>
);
}
}```
Before doing the API Call you can clean the DataSource array that you have.
// API CALL
_doApiCall = () => {
this.setState({
spinner: true,isLoading:true,
dataSource: []
});
...
You can handle that in 2 different ways.
whenever you call _doApiCall function, change initial value of dataSource as an empty array.
_doApiCall = () => {
this.setState({
spinner: true,
isLoading: true,
dataSource: []
});
}
Using ternary operator, diaplay spinner when data loading & if not diaplay faltlist
{
this.state.isLoading ?
<Show your Spinner /> :
<FlatList
data={this.state.dataSource}
renderItem={renderItem}
refreshing={this.state.isLoading}
onRefresh={this._doApiCall}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={1000}
initialNumToRender={1}
removeClippedSubviews={false}
keyExtractor={this._keyExtractor}
/>
}
Feel free for doubt. hope this helps you.

How to Fetch APIs and Print first element in response Array?

My React Native app fetch API data and I need to print the first index of response but it's not, and gets all of the "ozone" for example in all child of the parent Array and when I print val[0] when Mapping I have nothing printed
My Code|
export default class App extends Component {
constructor(props) {
super(props);
this.state = { isLoading: true, dataSource: null };
}
async componentDidMount() {
let API_WEATHER =
"https://api.weatherbit.io/v2.0/forecast/daily?city=Raleigh,NC&key={API_KEY}";
fetch(API_WEATHER)
.then(response => response.json())
.then(responseJson => {
console.log(responseJson.data);
this.setState({
isLoading: false,
dataSource: responseJson.data
});
})
.catch(error => {
console.log(error);
});
}
render() {
if (this.state.isLoading) {
return (
<View style={{ flex: 1, padding: 20 }}>
<ActivityIndicator size="large" />
</View>
);
}
let weather= this.state.dataSource.map((val, key) => {
return (
<Text key={key}>
{val.ozone}
</Text>
);
});
return (
<ScrollView style={styles.container}>
<ScrollView>
<View>
<Text>{weather}</Text>
</View>
</ScrollView>
</ScrollView>
);
}
In this part of the code when i log the respone JSON obj
.then(responseJson => {
console.log(responseJson.data);
console.log(responseJson.data[0]);
console.log(responseJson.data[0].datetime);
}
i have what i need, but when print them in View i have Erroe
look at the Images
You're probably the first key of the object.
obj[Object.keys(obj)[0]];
Also, you can use
Try the for … in loop and break after the first iteration
for (var prop in object) {
// object[prop]
break;
}

React Native CameraRoll.getPhotos API doesn't render the results

Hi I have the following class where I am trying to get the photos from camera roll and display it.
class CameraRollProject extends Component {
constructor(props) {
super(props);
this.state = {
images: []
};
}
componentWillMount() {
const fetchParams = {
first: 25,
};
CameraRoll.getPhotos(fetchParams, this.storeImages, this.logImageError);
}
storeImages(data) {
const assets = data.edges;
const images = assets.map((asset) => asset.node.image);
this.state.images = images;
}
logImageError(err) {
console.log(err);
}
render() {
return (
<ScrollView style={styles.container}>
<View style={styles.imageGrid}>
{ this.state.images.map((image) => <Image style={styles.image} source={{ uri: image.uri }} />) }
</View>
</ScrollView>
);
}
};
export default CameraRollProject;
The issue is my render function is getting called before my CameraRoll.getPhotos promise get resolved. So I don't get any photos.
To solve this issue I changed my program into following
render() {
return CameraRoll.getPhotos(fetchParams, this.storeImages, this.logImageError)
.then(() => {
return (
<ScrollView style={styles.container}>
<View style={styles.imageGrid}>
{ this.state.images.map((image) => <Image style={styles.image} source={{ uri: image.uri }} />) }
</View>
</ScrollView>
);
});
}
However this give me the following error
What can I do in above situation? How can I make sure the render only works after the CameraRoll.getPhotos get resolved.
So I resolved this issue. The main reason for my problem was I was not using CameraRoll.getPhotos properly as a Promise. I was passing incorrect parameter inside the function. To solve this I got rid of the following functions
storeImages(data) {
const assets = data.edges;
const images = assets.map((asset) => asset.node.image);
this.state.images = images;
}
logImageError(err) {
console.log(err);
}
And make my CameraRoll.getPhotos like the following
CameraRoll.getPhotos({first: 5}).then(
(data) =>{
const assets = data.edges
const images = assets.map((asset) => asset.node.image);
this.setState({
isCameraLoaded: true,
images: images
})
},
(error) => {
console.warn(error);
}
);
Here is my complete code to get pictures from CameraRoll in react-native just in case anyone interested
class CameraRollProject extends Component {
constructor(props) {
super(props);
this.state = {
images: [],
isCameraLoaded: false
};
}
componentWillMount() {
CameraRoll.getPhotos({first: 5}).then(
(data) =>{
const assets = data.edges;
const images = assets.map((asset) => asset.node.image);
this.setState({
isCameraLoaded: true,
images: images
})
},
(error) => {
console.warn(error);
}
);
}
render() {
if (!this.state.isCameraLoaded) {
return (
<View>
<Text>Loading ...</Text>
</View>
);
}
return (
<ScrollView style={styles.container}>
<View style={styles.imageGrid}>
{ this.state.images.map((image) => <Image style={styles.image} source={{ uri: image.uri }} />) }
</View>
</ScrollView>
);
}
};
export default CameraRollProject;
I think you should use react-native-image-picker
You have many parameters to retrieve a picture as you wish
selectPhotoTapped() {
const options = {
title: 'Choose a picture',
cancelButtonTitle: 'Back',
takePhotoButtonTitle: 'Take a picture...',
chooseFromLibraryButtonTitle: 'Choose from my pictures..',
quality: 1,
maxWidth: 300,
maxHeight: 300,
allowsEditing: true,
mediaType: 'photo',
storageOptions: {
skipBackup: true
}
}
it is much easier to handle than CameraRollProject, and the documentation is very well explained. for what you would do it suits perfectly. (It works on iOS and Android)
One way to do this would be to use a ListView rather than a Scrollview because you can utilize a datasource. Here is a sample of how you could do this:
constructor(props) {
super(props);
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
this.state = { dataSource: ds.cloneWithRows([]) };
this.loadPhotos();
}
loadPhotos() {
const fetchParams = {
first: 25,
};
CameraRoll.getPhotos(fetchParams).then((data) => {
this.state.dataSource.cloneWithRows(data.edges);
}).catch((e) => {
console.log(e);
});
}
This way, you render an empty list (and a loading state for good UX) and then once the fetch has completed you set the data in the ListView.
If you want to stick with the ScrollView and the mapped images, you would also need some sort of loading state until the photos load. Hope this helps.

Categories

Resources