React Native Flatlist ListItem called many times after initial render - javascript

Flatlist component as below
<FlatList
data={this.state.data}
keyExtractor={item => item.ID.toString()}
renderItem={this.renderItem}
onRefresh={this.onRefresh}
refreshing={this.state.refreshing}
ListFooterComponent={this.renderFooter}
/>
renderItem = ({ item }) => {
return (
<ListElement onPress={() => this.rowData(item)} item={item}/>
);
};
ListItem Component
import React from "react";
import { Image } from "react-native";
import { Left, Right, Body, Text, ListItem } from "native-base";
import { widthPercentageToDP as wp, heightPercentageToDP as hp } from "react-native-responsive-screen";
import Thumbnails from "../components/Thumbnails";
import styles from "../styles/HomeStyles";
import GlobalStyles from "../constants/GlobalStyles";
class ListElement extends React.Component {
componentDidMount(){
console.log(this.props.item.ID)
}
shouldComponentUpdate(){
return false;
}
render() {
return (
<ListItem onPress={this.props.onPress} thumbnail style={styles.listItem}>
<Left>
<Thumbnails imageURI={require("../../assets/images/female2.png")} />
</Left>
<Body style={{ borderBottomColor: "transparent", top: 2 }}>
<Text
style={{
fontFamily: GlobalStyles.primaryFont,
fontSize: hp("1.85%"),
}}
>
{this.props.item.FirstName} {this.props.item.LastName}
</Text>
<Text
note
style={{
fontFamily: GlobalStyles.primaryFont,
color: "#666666",
fontSize: hp("1.6%"),
}}
>
{this.props.item.Title}
</Text>
</Body>
<Right>
<Image
style={styles.rightArrow}
source={require("../../assets/images/arrowRight.png")}
/>
</Right>
</ListItem>
);
}
}
export default ListElement;
I tried to populate api data on the flatlist. Please refer my above code for implementation. Currently I am facing rerendering issues as mentioned below.
My listitem componentDidMount is invoking multiple times on scroll after even intial render of all listitems.
I have tried PureComponent and shouldComponentUpdate. Thanks in advance.

An Update
Please Find the entire code of Flatlist component
import React, { PureComponent } from "react";
import { View, FlatList } from "react-native";
import { ListItem } from "react-native-elements";
import FL from "../screens/FL";
import FL1 from "../screens/Fl1";
class FlatListDemo extends PureComponent {
constructor(props) {
super(props);
this.state = {
loading: false,
data: [],
error: null,
refreshing: false,
};
}
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteRequest = () => {
const url = `https://jsonplaceholder.typicode.com/posts`;
this.setState({ loading: true });
fetch(url)
.then((res) => res.json())
.then((res) => {
this.setState({
data: res,
error: res.error || null,
loading: false,
refreshing: false,
});
})
.catch((error) => {
this.setState({ error, loading: false });
});
};
renderItem = ({ item }) => {
return <ListElement onPress={() => this.rowData(item)} item={item} />;
};
render() {
if (this.state.loading === true) {
return <View></View>;
} else {
return (
<View>
<FlatList
data={this.state.data}
keyExtractor={(item) => item.ID.toString()}
renderItem={this.renderItem}
onRefresh={this.onRefresh}
refreshing={this.state.refreshing}
ListFooterComponent={this.renderFooter}
/>
</View>
);
}
}
}
export default FlatListDemo;

Try the following:
add to state a boolean value (you can name it "wasFetched").
Then change the componentDidMount like this:
componentDidMount() {
if(this.state.wasFetched === false) {
this.setState({ wasFetched: true }, () => this.makeRemoteRequest())
}
}

Related

Button that leads to another screen/page in react native

i'am fairly new to react native and i'm doing a social media clone. I've got my navigator and the login via a database done , but i was wondering if there was a way to link a page/screen to another one by pressing a button .
Here is my code for the home screen after logging in :
import React, {Component} from 'react';
import {View, Text, FlatList} from 'react-native';
import AsyncStorage from '#react-native-async-storage/async-storage';
class HomeScreen extends Component {
constructor(props){
super(props);
this.state = {
isLoading: true,
listData: []
}
}
componentDidMount() {
this.unsubscribe = this.props.navigation.addListener('focus', () => {
this.checkLoggedIn();
});
this.getData();
}
componentWillUnmount() {
this.unsubscribe();
}
getData = async () => {
const value = await AsyncStorage.getItem('#session_token');
return fetch("http://localhost:3333/api/1.0.0/search", {
'headers': {
'X-Authorization': value
}
})
.then((response) => {
if(response.status === 200){
return response.json()
}else if(response.status === 401){
this.props.navigation.navigate("Login");
}else{
throw 'Something went wrong';
}
})
.then((responseJson) => {
this.setState({
isLoading: false,
listData: responseJson
})
})
.catch((error) => {
console.log(error);
})
}
checkLoggedIn = async () => {
const value = await AsyncStorage.getItem('#session_token');
if (value == null) {
this.props.navigation.navigate('Login');
}
};
render() {
if (this.state.isLoading){
return (
<View
style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}>
<Text>Loading..</Text>
</View>
);
}else{
return (
<View
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}}>
<Text>Welcome to The app </Text>
</View>
);
}
}
}
export default HomeScreen;
Now ideally i would want a button in my else statement which could lead me to another screen (eg main screen of the app ) after logging in .
App.js :
import 'react-native-gesture-handler';
import React, { Component } from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createDrawerNavigator } from '#react-navigation/drawer';
import HomeScreen from './screens/home';
import LoginScreen from './screens/login';
import SignupScreen from './screens/signup';
import LogoutScreen from './screens/logout';
const Drawer = createDrawerNavigator();
class App extends Component{
render(){
return (
<NavigationContainer>
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="Login" component={LoginScreen} />
<Drawer.Screen name="Signup" component={SignupScreen} />
<Drawer.Screen name="Logout" component={LogoutScreen} />
</Drawer.Navigator>
</NavigationContainer>
);
}
}
export default App;
If you're new to react native, I'd suggest that you give functional components and hooks a try, this is how your code would look like as a functional component after some clean up
(The answer is included in component)
import React, { Component, useEffect, useState } from "react";
import { View, Text, FlatList, Button } from "react-native";
import AsyncStorage from "#react-native-async-storage/async-storage";
const HomeScreen = (props) => {
const [isLoading, setIsLoading] = useState(true);
const [listData, setListData] = useState([]);
useEffect(() => {
const subscription = props.navigation.addListener("focus", () => {
checkLoggedIn();
});
getData();
return subscription.remove(); //similar to component unmount, syntax might be different based on your version
}, []);
const getData = async () => {
setIsLoading(true)
const value = await AsyncStorage.getItem("#session_token");
return fetch("http://localhost:3333/api/1.0.0/search", {
headers: {
"X-Authorization": value,
},
})
.then((response) => {
if (response.status === 200) {
return response.json();
} else if (response.status === 401) {
props.navigation.navigate("Login");
} else {
throw "Something went wrong";
}
})
.then((responseJson) => {
setIsLoading(false);
setListData(responseJson);
})
.catch((error) => {
console.log(error);
});
};
const checkLoggedIn = async () => {
const value = await AsyncStorage.getItem("#session_token");
if (value == null) {
props.navigation.navigate("Login");
}
};
return (
<>
{isLoading ? (
<View
style={{
flex: 1,
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}
>
<Text>Loading..</Text>
</View>
) : (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
}}
>
<Text>Welcome to The app </Text>
<Button
title="Next Page"
onPress={() => {
props.navigation.navigate("yourScreenName");
}}
/>
</View>
)}
</>
);
};
export default HomeScreen;
since you screens are
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="Login" component={LoginScreen} />
<Drawer.Screen name="Signup" component={SignupScreen} />
<Drawer.Screen name="Logout" component={LogoutScreen} />
//you can add more screens here just make sure to import them first
//example
<Drawer.Screen name="Example" component={ExampleScreen} />
// name will be the name you use to navigate to that screen
// component should have the screen you imported
then you should pass one of those names to props.navigation.navigate() so the button would be
<Button
title="Next Page"
onPress={() => {
props.navigation.navigate("Login");
}}
/>
If you want to stick with a class component, then :
import {TouchableOpacity} from "react-native"
In the else part,
<TouchableOpacity style ={{width:100, height:100}} onPress={()=>{this.props.navigation.navigate("screenname")}}></TouchableOpacity>
TouchableOpacity is a blank space on the screen, which can be styled easily, and you have an onPress event. Basically it's like a better version of a button. You can also place other components inside the TouchableOpacity, and make the whole thing clickable

React native - Invariant violation-tried to get frame for out of range index

I have tried to search other posts and forums for the solution to this error. However, either no one has solved those issues or the issues are not really doing the same thing as I am doing. I am getting an error saying " Invariant violation-tried to get frame for out of range index". This happens when I try to input the data into the flatlist from the poke api. Please see code below.
import React, { useState } from "react";
import { View, Text , Button, FlatList, ActivityIndicator } from "react-native";
import { GlobalStyles } from "../styles/GlobalStyles";
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
dataSource: []
}
}
componentDidMount() {
fetch("https://pokeapi.co/api/v2/pokemon/")
.then((res)=> res.json())
.then((response)=> {
this.setState({
isLoading: false,
dataSource: response
})
console.log(response)
})
}
render() {
const showIndicator = this.state.isLoading == true ? <ActivityIndicator size="large" color="#0000ff" /> : null;
return(
<View style={GlobalStyles.container}>
<View style={GlobalStyles.activityIndicator}>{showIndicator}</View>
<FlatList data={this.state.dataSource} renderItem={({item})=> {
return(
<View>
<Text>{item.name}</Text>
</View>
)
}}/>
<Button onPress={()=> this.props.navigation.navigate("About")} title="Go to about"/>
</View>
)
}
}
export default Home;
The thing you are doing wrong is you are sending this.state.datasource is your data attribute, you need to send this.state.dataSource.results
import React, { useState } from "react";
import { View, Text , Button, FlatList, ActivityIndicator } from "react-native";
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
dataSource: []
}
}
componentDidMount() {
fetch("https://pokeapi.co/api/v2/pokemon/")
.then((res)=> res.json())
.then((response)=> {
this.setState({
isLoading: false,
dataSource: response
})
console.log(response)
})
}
render() {
const showIndicator = this.state.isLoading == true ? <ActivityIndicator size="large" color="#0000ff" /> : null;
return(
<View style={{marginTop:100}}>
<View>{showIndicator}</View>
<FlatList data={this.state.dataSource.results}
renderItem={({item})=> {
console.log("item is item",item);
return(
<View>
<Text>{item.name}</Text>
</View>
)
}}/>
<Button onPress={()=> this.props.navigation.navigate("About")} title="Go to about"/>
</View>
)
}
}
export default Home;
Hope this helps!

Checkbox will not render on state update in React Native

I am trying to get a specific checkbox to check and uncheck.
This is the parent component:
import React, { Component } from 'react';
import { View, Text, Modal, TouchableHighlight } from 'react-native'
import { loadLeagues } from '../actions'
import { connect } from 'react-redux'
import Check from './CheckBox'
class LeagueSelect extends Component {
constructor(props) {
super(props)
this.state = {
modalVisible: false,
checked: []
}
}
// state = {
// modalVisible: false,
// checked: []
// }
setModalVisible(visible) {
this.setState({modalVisible: visible})
this.props.league.map(
(v, i) => {
return this.state.checked.push(false)
}
)
}
componentDidMount() {
this.props.loadLeagues()
}
changeCheck = (index) => {
newChecked = this.state.checked.splice(index, 1, !this.state.checked[index])
console.log('newChecked', this.state.checked)
this.setState({ checked: newChecked })
console.log('league checked state', this.state.checked)
}
render() {
return (
<View style={{marginTop: 22}}>
<Modal
animationType="slide"
transparent={false}
visible={this.state.modalVisible}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}
>
<View style={{marginTop: 100}}>
<TouchableHighlight
onPress={() => {
this.setModalVisible(!this.state.modalVisible);
}}
>
<Text>Hide Modal</Text>
</TouchableHighlight>
<Text>Leagues</Text>
{this.props.league === null ?'' : this.props.league.map(
(v, i) => {
return(
<View>
<Check
checked={this.state.checked[i]}
index={i}
changeCheck={this.changeCheck}
/>
<Text>{v.acronym}</Text>
</View>
)
}
)}
</View>
</Modal>
<TouchableHighlight
onPress={() => {
this.setModalVisible(true);
}}>
<Text>Show Modal</Text>
</TouchableHighlight>
</View>
);
}
}
function mapStateToProps(state) {
return {
league: state.league.league
}
}
export default connect(mapStateToProps, { loadLeagues })(LeagueSelect)
This is the child component that I'm passing the index of the the checkbox into to update; I'm also passing in the function that updates the state of the checkbox at the specific index:
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { CheckBox } from 'native-base'
export default class Check extends Component {
constructor(props) {
super(props)
this.state = {
cards: []
}
}
localChange = () => {
this.props.changeCheck(this.props.index)
}
render() {
return(
<CheckBox
checked={this.props.checked}
onPress={this.localChange}
/>
)
}
}
I can see that the state updates when press the checkbox through the console logs that I have setup, but the checkbox isn't updating based off of the new state.

AsyncStorage data not displayed in FlatList

I create an application that retrieves data from a URL (an array of objects) and display it in FlatList. I'm a beginner and therefore I don't use Redux or other for the moment. I would like to store my data in AsyncStorage and display them.
I tried this, but my data are not displayed int FlatList:
import React, {Component} from 'react';
import {ScrollView, View, FlatList, Image, ActivityIndicator, AsyncStorage} from 'react-native';
import axios from "axios";
import {ListItem} from "react-native-elements";
import {createAppContainer, createStackNavigator} from "react-navigation";
import AppConfig from "../../AppConfig";
import Keys from "../../data/Constants/Storage";
import PronosticsDetailsScreen from "../../screens/PronosticsDetailsScreen";
class MontanteTab extends Component {
state = {
errors: null,
isLoading: true,
pronostics: [],
};
async componentDidMount() {
const isConnected = true;
if (isConnected) {
await this.loadPronostics();
}
try {
this.setState({pronostics: JSON.parse(await AsyncStorage.getItem(Keys.pronosticsMontante))});
} catch (error) {
console.log(error);
}
}
loadPronostics() {
this.setState({isLoading: true, error: null});
return axios.get(AppConfig.apiUrl + 'montante').then(async response => {
await AsyncStorage.setItem(Keys.pronosticsMontante, JSON.stringify(this.state.pronostics));
this.setState({isLoading: false});
}).catch(error => {
this.setState({isLoading: false, error: error.response});
console.log(error);
});
}
render() {
if (this.state.isLoading === true) {
return (
<View style={{flex: 1, padding: 20}}>
<ActivityIndicator/>
</View>
)
}
return (
<View>
<ScrollView>
<View>
<FlatList
data={this.state.pronostics}
extraData={this.state.pronostics}
keyExtractor={(item, index) => index.toString()}
renderItem={({item}) => (
<ListItem
key={item.id}
roundAvatar
badge={{
value: item.statut,
textStyle: {color: '#fff'},
containerStyle: {marginRight: 0, backgroundColor: item.couleur}
}}
avatar={<Image
source={{uri: AppConfig.imagesPronosticsUrl + item.image}}
style={{borderRadius: 50, height: 50, width: 50}}/>}
title={item.competition}
subtitle={item.equipe_domicile + ' - ' + item.equipe_exterieur}
onPress={() => this.props.navigation.navigate('PronosticsDetails', {
item,
})}
/>
)}
/>
</View>
</ScrollView>
</View>
);
}
}
What's the problem please ?
I'm not an expert here, but...
One "odd" thing about FlatLists is that they are Pure Components so they don't always rerender when you expect. FlatList helps you out here and provides a property called extraData. You can use this to tell FlatList what to watch to know if there is an important change. So, try adding:
extraData={ this.state.pronostics }
to your FlatList.
The problem is solved.
I replaced :
await AsyncStorage.setItem(Keys.pronosticsMontante, JSON.stringify(this.state.pronostics));
by :
await AsyncStorage.setItem(Keys.pronosticsMontante, JSON.stringify(response.data));

React Native - Modal with Flatlist items

I'm making a modal that will popup when the user clicks a flatlist button or items, and there the user will see the details about the item he/she clicks. Basically, I want to pass the items of flatlist to modal.
I'm actually done with the popup of the modal, now I have to show the details like menu description and menu price. I've found a post here in stackoverflow and I follow everything in there but I am having an error regarding with an " id ", and I can't figure out how to fix it.
Here is my code
Details.js
import React, {Component} from 'react';
import {Text, TouchableHighlight, View,
StyleSheet, Platform, FlatList, AppRegistry,
TouchableOpacity, RefreshControl, Dimensions, Modal, TextInput, TouchableWithoutFeedback, Keyboard
} from 'react-native';
import AddModal from '../Modal/AddModal';
var screen = Dimensions.get('window');
const DismissKeyboard = ({ children }) => (
<TouchableWithoutFeedback onPress = {() => Keyboard.dismiss()}>
{children}
</TouchableWithoutFeedback>
);
export default class Details extends Component {
static navigationOptions = {
title: ''
};
constructor()
{
super ()
this.state = {
data: [],
showModal: false,
id: null,
}
}
fetchData = async() => {
const { params } = this.props.navigation.state;
const response_Cat = await fetch('http://192.168.254.101:3307/categories/' + params.id);
const category_Cat = await response_Cat.json();
this.setState({data: category_Cat});
};
componentDidMount() {
this.fetchData();
};
_onRefresh() {
this.setState({ refreshing: true });
this.fetchData().then(() => {
this.setState({ refreshing: false })
});
};
_onPressItem(id) {
this.setState({
modalVisible: true,
id: id
});
}
_renderItem = ({item}) => {
return (
<TouchableHighlight
id = { item.menu_desc }
onPress = { this._onPressItem(item.menu_desc) }
>
<View>
<Text>{ this.state.id }</Text>
</View>
</TouchableHighlight>
)
};
render() {
const { params } = this.props.navigation.state;
return (
<View style = { styles.container }>
<AddModal
modalVisible = { this.state.modalVisible }
setModalVisible = { (vis) => { this.setModalVisible(vis) }}
id = { this.state.id }
/>
<FlatList
data = { this.state.data }
renderItem = { this._renderItem }
keyExtractor={(item, index) => index}
/*refreshControl = {
<RefreshControl
refreshing = { this.state.refreshing }
onRefresh = { this._onRefresh.bind(this) }
/>
}*/
/>
</View>
);
}
}
const styles = StyleSheet.create({
...
})
//AppRegistry.registerComponent('Details', () => Details);
AddModal.js
import React, {Component} from 'react';
import {Text, TouchableHighlight, View,
StyleSheet, Platform, FlatList, AppRegistry,
TouchableOpacity, RefreshControl, Dimensions, TextInput, Modal
} from 'react-native';
export default class AddModal extends Component {
constructor(props) {
super(props);
this.state = {
showModal: false,
id: null
};
}
componentWillReceiveProps(nextProps) {
this.setState({
showModal: nextProps.showModal,
id: nextProps.id,
})
}
render() {
return (
<Modal
animationType="slide"
transparent={ true }
visible={ this.state.modalVisible }
onRequestClose={() => { this.props.setModalVisible(false) }}>
<View>
<View>
<Text>{ this.state.id }</Text>
<TouchableHighlight
onPress = {() => { this.props.setModalVisible(false) }}
>
<Text>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
)
}
}
Just wanted to pointout an issue in your code (not related to 'id' error, id error already answer by digit). In the renderItem function, you are calling onPress = { this._onPressItem(item.menu_desc) }, it should be changed to
onPress = { () => this._onPressItem(item.menu_desc) }
I guess, you will call the onPressItem when user click on list item.
EDIT:
I have made a couple of changes to make your code working. Here are the changes.
In your AppModal.js, you are setting modal visibility in showModal: nextProps.showModal , but in the <Modal ...> you have set visible
= { this.state.modalVisible }. Also in Details.js you have written <AddModal modalVisible ...>.
First I changed showModal to modalVisible in Details.js and in AppModal.js.
Details.js
constructor()
{
super ()
this.state = {
data: [],
modalVisible: false,
id: null,
}
}
Then I changed _onPressItem(id) to _onPressItem = (id)
Made changes in renderItem as
<TouchableHighlight
id = { item.enName }
onPress = { () => this._onPressItem(item.enName) }
>
in render function I have set modal visibility as
<AddModal
...
setModalVisible = { (vis) => { this.setState({
modalVisible: vis
})
}}
...
/>
AppModal.js
Changed showModal to modalVisible
constructor(props) {
super(props);
this.state = {
modalVisible: props.modalVisible,
d: null
};
}
componentWillReceiveProps(nextProps) {
this.setState({
modalVisible: nextProps.modalVisible,
id: nextProps.id,
})
}
In the constructor, I have added modalVisible: props.modalVisible.
Hope this will help!
I guess item.menu_desc is an id of each item so it must be {item.menu_desc} not {id}. Change it like below
_renderItem = ({item}) => {
return (
<TouchableHighlight
id = { item.menu_desc }
onPress = { this._onPressItem(item.menu_desc) }
>
<View>
<Text>{ item.menu_desc }</Text>
</View>
</TouchableHighlight>
)
};

Categories

Resources