Trouble on adding items to Flatlist, React Native - javascript

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

Related

Flatlist adds multiple times the same items

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}
}

this2.props.addToList is undefined, List creation error

Diet.js
export class Diet extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
};
this.addToList = this.addToList.bind(this);
}
addToList(item) {
const list = [...this.state.list, item];
this.setState({ list });
}
render() {
<FoodCreate addToList={this.addToList} />
return (
<FoodList items={this.state.list} />
)}
FoodCreate
export class FoodCreate extends Component {
constructor(props) {
super(props);
this.state = {
FoodName: "",
calories: 0,
};
}
render() {
return (
<Button transparent>
<Icon
name="checkmark"
style={{ fontSize: 25, color: "red" }}
onPress={() => this.props.addToList(FoodName, calories)}
/>
</Button>
<TextInput
placeholder="Food Name"
placeholderTextColor="white"
style={styles.inptFood}
value={FoodName}
onChangeText={(FoodName) => this.setState({ FoodName: FoodName })}
/>
<TextInput
placeholder="Calories"
placeholderTextColor="white"
style={styles.inptMacros}
keyboardType="numeric"
value={calories}
maxLength={5}
onChangeText={(calories) => this.setState({ calories: calories })}
/>
FoodList
export class FoodList extends Component {
render() {
return (
<Content>
<List>
<ListItem itemDivider>
<Text>Food</Text>
{this.props.items.map((item, index) => {
return (
<ListItem key={index}>
<Text>{item.FoodName}</Text>
<Text>{item.calories}</Text>
</ListItem>
);
})}
</ListItem>
</List>
</Content>
);
}
}
export default FoodList;
Hi, I'm new to programming and React Native, so I'm trying to create a Grocery List by letting the user type FoodName and Calories and pressing the Icon: Check in FoodCreate page, and List it in the FoodList page, at the moment when I run the code gives me back an error: _this2.props.addToList is not a function, I've tried many solutions but I'm not sure where the error is.
class FoodCreate extends Component {
render() {
return (
<Button title="aaa" onPress={() => this.props.addToList('name')}></Button>
);
}
}
export default class Diet extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
};
this.addToList = this.addToList.bind(this);
}
addToList(item) {
const list = [...this.state.list, item];
this.setState({list});
}
render() {
return <FoodCreate addToList={this.addToList} />;
}
}
I use the above code and didn't get the error
But I think you can have a better code
Don't use this.addToList = this.addToList.bind(this);, you can convert addToList to arrow function and remove this line
addToList = item => {
const list = [...this.state.list, item];
this.setState({list});
};

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(
...
...
...
)
}

Navigate to another screen from Flat list item getting pressed

I've been using wix/react-native-navigation package to navigate between screens and handling the stack properly.
Moving across screens is pretty straightforward, firing those transitions when a button gets pressed. But the issue comes up when I have a FlatList and I want to push to a new screen when the user taps an item from the list, looks like the navigator props injected at the beginning is lost or in another context than the onPress callback event;
Here is the sample code
class AlertType extends React.PureComponent {
_onPress = () => {
this.props.onPressItem(this.props.itemId, this.props.itemName, this.props.itemImageUrl);
};
render() {
return (
<TouchableOpacity { ...this.props }
onPress={ this._onPress }
style={ itemStyle.cardContainer }>
<View style={ itemStyle.mainContainer }>
<View style={{ width: 10 }}/>
<Image
source={{ uri: NET.HOST + this.props.itemImageUrl }}
style={{ width: 45, height: 45 }}
/>
<View style={{ width: 10 }}/>
<Text style={ itemStyle.itemText }>{ this.props.itemName }</Text>
</View>
</TouchableOpacity>
);
}
}
class AlertsScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
alertTypes: null,
}
}
_onAlertTypePressed(typeId: string, typeName: string, imageUrl: string){
this.props.navigator.push({
screen: 'prod.screens.AlertsCreator',
title: 'Alert',
passProps: {
alertId: typeId,
alertName: typeName,
alertImage: imageUrl
}
});
}
_renderListItem = ({ item }) => (
<AlertType
itemName={ item.titulo }
itemId={ item.key }
itemImageUrl={ item.url }
onPressItem={ this._onAlertTypePressed }
/>
);
render() {
return (
<View style={ styles.mainContainer }>
<FlatList
data={ this.state.alertTypes }
ItemSeparatorComponent={ () => <View style={{ height: 5 }}/> }
renderItem={ this._renderListItem }
/>
</View>
);
}
const mapSessionStateToProps = (state, ownProps) => {
return {
session: state.session
};
}
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(actions, dispatch)
};
}
export default connect(mapSessionStateToProps, mapDispatchToProps)(AlertsScreen)
This approach produces the next error
There have to be something I'm missing, I know this.props.navigator is not undefined, but inside on _onAlertTypePressed the navigator prop is undefined.
The problem is that you pass function to component without binding it to the current context.
You should pass:
this._onAlertTypePressed.bind(this);
another approach is binding your functions in the constructor:
constructor(props) {
this._onAlertTypePressed = this._onAlertTypePressed.bind(this);
}
I've had this happen before also.
I had to declare navigator between the render and return blocks
render() {
const navigator = this.props.navigator
return()}}
then pass navigator through when calling _onAlertTypePressed
() => _onAlertTypePressed(navigator)
then use navigator vs this.props.navigator inside _onAlertTypePressed

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