Flatlist adds multiple times the same items - javascript

I'm trying to make a small App to count Calories using React Navigation and React Redux. The goal is for the user to search an item in the SearchBar, tap in one of the food items which will move him to the Food page, and from that page tap on the checkmark to add the food in the Flatlist and send him back to the Main Screen (Diet). I'm using the state showList to show the Flatlist when tapping on the SearchBar then use React Navigation to navigate and send params to the Food screen and then use React Redux to update the global state foodList and add that item in the main Flatlist (in Diet screen) through the checkmark Button, I also use getDerivedStateFromprops to update the state foodList in the Diet screen. The problem is that when I add an item, it adds multiple times the same item and when I add a new one it adds again a lot of times the same items. I used to bring the params back to the Main screen using React Navigation but I thought I could fix this problem by using Redux, and I was wrong because the problem is still there.
Main Screen
class Diet extends Component {
constructor(props) {
super(props);
this.camera = null;
this.barcodeCodes = [];
this.state = {
showList: false,
data: [],
searchValue: "",
foodList: [],
foodName: null,
};
}
static getDerivedStateFromProps(props, state) {
if (props?.foodList) {
return {
foodList: [...state.foodList, ...props.foodList],
};
}
return null;
}
updateSearch = (value) => {
this.setState({ searchValue: value });
if (value.trim() !== "") {
axios
.get(
`https://api.edamam.com/api/food-database/v2/parser?ingr=${value}&app_id=2626c70d&app_key=0c0f87ae4e5437621363ecf8e7ea80ae&page=20`
)
.then((res) => {
this.setState({ data: res.data.hints });
})
.catch((error) => {
console.log(error.response.data);
});
}
};
return (
<SearchBar
platform={Platform.OS === "ios" ? "ios" : "android"}
placeholder="Search Food..."
onChangeText={this.updateSearch}
value={searchValue}
onFocus={() => this.setState({ showList: true })}
onCancel={() => this.setState({ showList: false })}
/>
{this.state.showList === true ? (
<View>
<FlatList
data={this.state.data.map((item) => item.food)}
renderItem={({ item }) => (
<ListItem>
<TouchableOpacity
onPress={() =>
{this.props.navigation.navigate("Food", {
id: item.foodId,
brand: item.brand,
title: item.label,
calories: item.nutrients.ENERC_KCAL,
protein: item.nutrients.PROCNT,
fat: item.nutrients.FAT,
carbs: item.nutrients.CHOCDF,
}),
this.setState({showList:false})
}
}
>
<View>
<Text>{item.label}</Text>
<Text>{item.brand}</Text>
</View>
</TouchableOpacity>
</ListItem>
)}
keyExtractor={(item) => item.foodId}
/>
) : (
<FlatList ///Main Flatlist
data={this.state.foodList}
renderItem={({item}) => (
<View>
<TouchableOpacity
onPress={() => {
this.props.navigation.navigate(
"FoodNotModifible",
{
title: item.foodName,
calories: item.calories,
carbs: item.carbs,
protein: item.protein,
fat: item.fat,
numberServings: item.numberServings,
servingSize: item.servingSize,
}
);
}}
>
<Text>{item.foodName}</Text>
<Text>
{item.calories}
</Text>
<MaterialIcons name="arrow-forward-ios" />
</TouchableOpacity>
</View>
)}
keyExtractor={item => item.foodId}
/>
);
}
}
function mapStateToProps(store){
return{
foodList: store.userState.foodList
};
}
export default connect(mapStateToProps)(Diet);
Food Screen
class Food extends Component {
constructor(props) {
super(props);
this.state = {
foodList: this.props.foodList,
}
}
submitFood = () => {
let foodList= this.state.foodList;
foodList.push({
foodId: this.props.route.params.foodId,
foodName: this.props.route.params.title,
calories: this.props.route.params.calories,
carbs: this.props.route.params.carbs,
protein: this.props.route.params.protein,
fat: this.props.route.params.fat,
});
this.props.updateFoodList(foodList);
this.props.navigation.navigate("Diet");
};
render() {
const { brand, title, calories, protein, carbs, fat, foodId } = this.props.route.params;
return (
<Container>
<Header>
<Left>
<Button transparent>
<Icon
name="arrow-back"
onPress={() => this.props.navigation.goBack()}
/>
</Button>
</Left>
<Body>
<Title>Add Food</Title>
</Body>
<Right>
<Button transparent>
<Icon
name="checkmark"
onPress={this.submitFood}
/>
</Button>
</Right>
</Header>
<View>
<Text>
{JSON.stringify(title)}
</Text>
<Text>
{JSON.stringify(brand)}
</Text>
</View>
Redux Function
export const updateFoodList = (foodList) => {
return { type: ADD_FOOD, payload: foodList}
}

Related

Trouble on adding items to Flatlist, React Native

DIET (screen)
export class Diet extends Component {
constructor(props) {
super(props);
this.state = {
foodList: [],
};
}
render() {
return (
<View>
<List>
<FlatList
data={this.props.route?.params?.foodList}
keyExtractor={(item, index) => item.key.toString()}
renderItem={(data) => (
<ListItem>
<Button>
<Left>
<Text>{data.item.foodName}</Text>
</Left>
<Right>
<Text>{data.item.calories}</Text>
<Icon name="arrow-forward" />
</Right>
</Button>
</ListItem>
)}
/>
</List>
</View>
FOODCREATE (screen)
export class FoodCreate extends Component {
constructor(props) {
super(props);
this.state = {
food: null,
calories: null,
foodList: [],
};
}
submitFood = (food, calories) => {
this.setState(
{
foodList: [
...this.state.foodList,
{
key: Math.random(),
foodName: food,
calories: calories,
},
],
},
() => {
this.props.navigation.navigate("Diet", {
foodList: this.state.foodList,
});
}
);
};
render() {
return (
<Container>
<TextInput
placeholder="Food Name"
placeholderTextColor="white"
style={styles.inptFood}
value={this.state.food}
onChangeText={(food) => this.setState({ food })}
/>
<TextInput
placeholder="Calories"
placeholderTextColor="white"
style={styles.inptMacros}
keyboardType="numeric"
value={this.state.calories}
maxLength={5}
onChangeText={(calories) => this.setState({ calories })}
/>
<Button transparent>
<Icon
name="checkmark"
style={{ fontSize: 25, color: "red" }}
onPress={() => {
this.submitFood(this.state.food, this.state.calories);
}}
/>
</Button>
Hello everyone, I'm trying to make an app in which the user has to insert foodName and calories in the FoodCreate screen and once he taps the checkmark it will add the foodName and calories to the Flatlist in the Diet screen (when I launch Expo the first screen to appear is the Diet screen). When I insert the first food item everything goes fine, but when I want to insert another one, the one I inserted before disappears and it shows only the one I just inserted. I don't know if it's a problem related with the Flatlist or React Navigation. But the Flatlist won't keep the items I inserted.
The problem here is the way navigation works,
Everytime you open the FoodCreate screen the the component is mounted again and the FoodList is reset, so the newly added one would be the only item there, you return this as a parameter to Diet screen which will show only one item.
Heres a the better way to do it.
Move the state management to Diet screen
class Diet extends Component {
constructor(props) {
super(props);
this.state = {
foodList: [],
};
}
// Use this to update state.
static getDerivedStateFromProps(props, state) {
if (props.route.params?.food) {
return { foodList: [...state.foodList, props.route.params.food] };
}
return null;
}
And show the value in the state in the flatlist
<FlatList data={this.state.foodList} ...
Change submitFood like below to send only newly created item
submitFood = (food, calories) => {
this.props.navigation.navigate("Diet", {
food: {
key: Math.random(),
foodName: food,
calories: calories,
},
});
}
The easier way is to switch to functional components, you can refer the documentation here
https://reactnavigation.org/docs/params/#passing-params-to-a-previous-screen

undefined is not an object (this.props.navigation.getParam)

FoodCreate.js
export class FoodCreate extends Component {
state = {
food: null,
foodList: [],
};
submitFood = (food) => {
this.setState({
foodList: [
...this.state.foodList,
{
key: Math.random(),
name: food,
},
],
});
this.props.navigation.navigate("FoodList", {
foodList: this.state.foodList,
deleteFood: this.deleteFood,
});
};
deleteFood = (key) => {
this.setState({
foodList: [...this.state.foodList.filter((item) => item.key != key)],
});
};
render() {
return (
<Container>
<Header>
<Left>
<Button transparent>
<Icon
name="arrow-back"
onPress={() => this.props.navigation.goBack()}
style={{ fontSize: 25, color: "red" }}
/>
</Button>
</Left>
<Body>
<Title>Add Food</Title>
</Body>
<Right>
<Button transparent>
<Icon
name="checkmark"
style={{ fontSize: 25, color: "red" }}
onPress={() => {
this.submitFood(this.state.food);
}}
/>
</Button>
</Right>
</Header>
<View style={{ alignItems: "center", top: hp("3%") }}>
<TextInput
placeholder="Food Name"
placeholderTextColor="white"
style={styles.inptFood}
value={this.state.food}
onChangeText={(food) => this.setState({ food })}
/>
</View>
</Container>
);
}
}
export default FoodCreate;
FoodList.js
export class FoodList extends Component {
constructor(props) {
super(props);
this.state = {
foodList: [],
};
}
render() {
return (
<Button onPress={() => this.props.navigation.navigate("FoodCreate")}>
Press to insert food
</Button>
<FlatList
data={this.props.navigation.getParam("foodList")} <-------
keyExtractor={(item, index) => item.key.toString()}
renderItem={(data) => <ListItem itemDivider title={data.item.name} />}
/>
);
}
}
export default FoodList;
Hey everyone, I'm building a Diet App, the food gets created in FoodCreate.js by typing a food and pressing the checkmark Icon, and this will create the food and display it in the FoodList.js, keep it in mind that the first Screen to be displayed is FoodList.js. When I run the code I get the following error: undefined is not an object (this.props.navigation.getParam)
try
<FlatList
data={props.route.params.foodList} <-------
...
...
/>
you must see document here: https://reactnavigation.org/docs/params/

View not re-rendering after onPress

I'm trying to change the backgroundColor of a React Native Card component when onPress event is triggered. Although I'm seeing the change of the state on componentDidUpdate, I'm not visualising it.
I'm changing the value of the itemsPressed array when the onPress event is triggered. If the pressed item id is already in the array it removes it else it adds it into the array.
export default class Popular extends Component {
constructor(props) {
super(props);
this.togglePressed = this.togglePressed.bind(this);
this.state = {
categories: [],
itemsPressed: []
}
}
togglePressed = item => {
const id = item.id;
this.setState(({ itemsPressed }) => ({
itemsPressed: this.isItemPressed(item)
? itemsPressed.filter(a => a != id)
: [...itemsPressed, id],
}))
};
isItemPressed = item => {
const id = item.id;
return this.state.itemsPressed.includes(id);
};
componentDidMount() {
this.setState({
categories:this.props.categories,
});
}
componentDidUpdate(){
console.log(this.state.itemsPressed);
}
renderTabItem = ({ item,index }) => (
<TouchableOpacity
style={styles.category}
key={index}
onPress={() => this.togglePressed(item)}
>
<Card center
style={[styles.card,{backgroundColor:
this.isItemPressed(item)
? item.color
: 'gray'
}]}>
<Image source={item.icon} style={styles.categoryIcon}/>
</Card>
<Text size={12} center style={styles.categoryName}
medium color='black'
>
{item.name.toLowerCase()}
</Text>
</TouchableOpacity>
);
renderTab(){
const {categories} = this.state;
return (
<FlatList
horizontal = {true}
pagingEnabled = {true}
scrollEnabled = {true}
showsHorizontalScrollIndicator={false}
scrollEventThrottle={16}
snapToAlignment='center'
data={categories}
keyExtractor={(item) => `${item.id}`}
renderItem={this.renderTabItem}
/>
)
}
render() {
return (
<ScrollView>
{this.renderTab()}
</ScrollView>
);
}
}
I expected a visual change but I couldn't re render the renderTab().
Thank you!
Your FlatList has the property category as data source, so it only re-renders the cells if it detects a change in the category property. Your code however is only changing itemsPressed, so no cell is re-rendered.
You can tell the FlatList to listen for changes state.itemsPressed by specifying it in the extraData property:
extraData={this.state.itemsPressed}

How to pass a value to modal and show modal when click on flatlist item in react native

i want to pass a value from activity screen to modal screen. I am trying to open a screen when click on flatlist item an dpass value of item to modal,but it shows my detail before rendering modal screen,Here is my activity screen:-
<FlatList
data={this.state.myListDataSource}
renderItem={this._renderItem}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
keyExtractor={({item,index}) => item+index}
refreshControl={
<RefreshControl
refreshing={this.state.isRefreshing}
onRefresh={this.pullToRefresh}
/>
}
/>
<ListProducts
modalVisibility={this.state.listModalVisibility}
closeModal={() =>
this.setState({listModalVisibility:false})}
listName={this.state.listName}
listId={this.state.listId}
/>
handleListItemPress = (item) => {
this.setState({
listModalVisibility:true,
listName : item.name,
listId : item.list_id
})
showMessage('List Item : '+item.listId)
}
_renderItem = ({item}) => {
return(
<TouchableOpacity onPress={() => this.handleListItemPress(item)}>
<View >
<View>
<View style={{flexDirection:'row',marginBottom:2}}>
<ImageView
image={item.pictures[0]}
style={[{marginRight:2},styles.imageStyle]}
/>
<ImageView
image={item.pictures[1]}
style={[{marginLeft:2},styles.imageStyle]}
/>
</View>
<View style={{flexDirection:'row',marginTop:2}}>
<ImageView
style={[{marginRight:2},styles.imageStyle]}
image={item.pictures[2]}
/>
<ImageView
image={item.pictures[3]}
style={[{marginLeft:2},styles.imageStyle]}
/>
</View>
</View>
<View>
<TextViewNonClickable
textViewText={item.name}
/>
<TextViewNonClickable
textViewText={item.description}
/>
</View>
<Icon
name = 'more-vertical'
type = 'feather'
color = {color.colorWhite}
iconStyle={{padding:8}}
containerStyle = {{position:'absolute',top:8,right:8}}
onPress = {() => {
showMessage('Options')
}}
/>
</View>
</TouchableOpacity>
)}
This is my modal screen where i want to get list item id or name.But that screen shows details on the screen rather than rendering modal screen .
Here is my modal screen :-
export default class ListProducts extends Component {
constructor(props){
super(props)
this.state={
products : [],
refreshing : false,
listId : 256,
listName : props.name
}
}
_renderProducts = ({item}) => {
return(
<Product
image={item.image}
name={item.name}
price={item.price}
discountedPrice={item.discounted_price}
quantityAdded={item.quantity_added}
productId={item.product_id}
stock={item.stock}
/>
)
}
render() {
const {modalVisibility,closeModal,listName,listId} = this.props;
return (
<Modal
animationIn='bounceInRight'
animationOut='bounceOutRight'
isVisible={modalVisibility}
onBackButtonPress={closeModal}
>
<View style={{flex:1,backgroundColor:color.colorWhite}}>
<Header
placement='left'
leftComponent={
<FlatList
data={this.state.products}
renderItem={this._renderProducts}
keyExtractor={(item,index) => item+index}
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this.pullToRefresh}
/>
}
numColumns={3}
style={{paddingLeft:2,paddingRight:2}}
/>
</View>
</Modal>
)
}
}
Step 1: pass props from modal to class.
In modal like:
this.props.setItem(“sunny”)
Step 2: Get that props in class in render method where modal initialised.
<ModalName SetItem={item => console.log(item)} \>
I'm actually using 'Dialog' from 'react-native-simple-dialogs' for popup. It works better for me than 'Modal', but i think the logic is the same.
Here is a simplified example of a edit email popup that works for me:
import React from 'react';
import { StyleSheet, View, TextInput } from 'react-native';
import { Button, Text } from 'native-base';
import { Dialog } from 'react-native-simple-dialogs';
export default class EditEmailPopup extends React.Component {
constructor(props) {
super(props);
this.state = {
isModalVisible: this.props.isModalVisible,
};
}
componentWillUpdate() {
this.state.isModalVisible = this.props.isModalVisible;
}
_updateEmailAndCloseDialog() {
// update some data...
this._onCloseDialog();
}
_onCloseDialog() {
this.setState({ isModalVisible: false});
this.props.client(); //this is a function transfered from parent that controls the visibility of the dialog.
}
render() {
return (
<View>
<Dialog
visible={this.state.isModalVisible}
onTouchOutside={() => this._onCloseDialog()}
>
<View>
<Text style={styles.text}>{'Update Email text'}</Text>
<View style={styles.popupButtons}>
<Button
transparent
style={styles.cancelButton}
onPress={() => this._onCloseDialog()}
>
<Text> {'cancel'} </Text>
</Button>
<Button
style={styles.okButton}
onPress={() => this._updateEmailAndCloseDialog()}
>
<Text> {'ok'} </Text>
</Button>
</View>
</View>
</Dialog>
</View>
);
}
}
Here is how i add my Dialog in the parent view:
{this.state.emailModalVisibility ? (
<EditEmailPopup
isModalVisible={this.state.emailModalVisibility}
client={this.afterClosePopup}
/>
) : null}
while this.state.emailModalVisibility initiated in the constructor in state as 'false'.
function written in parent:
_afterClosePopup = () => {
this.setState({
emailModalVisibility: false
});
};
and binded in the constructor so 'this' will belong to parent:
constructor(props) {
super(props);
this.afterClosePopup = this._afterClosePopup.bind(this);
this.state = {
emailModalVisibility: false
};
}

How to pass props from FlatList item to Modal?

I have implemented a View component containing a FlatList, which renders TouchableHighlights. Also I have implemented a Modal component, which I'd like to import at various places including the component that renders the FlatList.
I have already managed to open the modal from outside (via handing over a prop for visibility, accessing it via nextProps and setting modals state value "modalVisible" to this) and closing it from inside (simply via changing it's state value "modalVisible").
BUT: I also want to pass data to the modal from each FlatLists item. An item rendered as a TouchableHighlight should open the modal onPress and the modal should contain data from the item (in the code below this would be the items id). How can I achieve passing data to the modal? I somehow can't get it to work using nextProps. This seems more to be an issue related to setting state from within a FlatLists item rather than the Modal.
Modal:
export default class ModalView extends React.Component {
constructor() {
super();
this.state = {
modalVisible: false,
id: null,
};
}
componentWillReceiveProps(nextProps) {
this.setState({
modalVisible: nextProps.modalVisible,
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>
)
}
}
FlatList rendering TouchableHighlights:
export default class RecentList extends React.Component {
constructor() {
super();
this.state = {
modalVisible: false,
id: null,
}
}
_onPressItem(id) {
this.setState({
modalVisible: true,
id: id,
});
};
_renderItem = ({item}) => {
return (
<TouchableHighlight
id={item.id}
onPress={this._onPressItem}
>
<View>
<Text>{id}</Text>
</View>
</TouchableHighlight>
)
};
render() {
let data = realm.objects('Example').filtered('completed = true')
.sorted('startedAt', true).slice(0, 10)
return (
<View>
<ModalView
modalVisible={ this.state.modalVisible }
setModalVisible={ (vis) => { this.setModalVisible(vis) }}
id={ this.state.id }
/>
<FlatList
data={data}
renderItem={this._renderItem}
keyExtractor={(item, index) => index}
/>
</View>
)
}
}
A small mistake you have missed ...
_renderItem = ({item}) => {
return (
<TouchableHighlight
id={item.id}
onPress={() => this._onPressItem(item.id)} // Your not sending the item.id
>
<View>
<Text>{id}</Text>
</View>
</TouchableHighlight>
)
};

Categories

Resources