useState array not updating correctly as it creates duplicate items - react-native

I am trying to update the useState() array by passing through value of TextInput (onChangeText). The code is working fine however whenever I am clinking onPress (addBook function) it is updating/ adding previous text in the array not current one. So, to add current text I have to click button twice which is creating dupliate items in my array.
I may be making very studpid misatke but cant find it. Can any one help. Below is my entire code.
import React, { useState } from "react";
import { TextInput, View, Dimensions, TouchableOpacity } from "react-native";
import { MaterialCommunityIcons } from "#expo/vector-icons";
import { Entypo } from "#expo/vector-icons";
const screenHeight = Math.round(Dimensions.get("window").height);
const AddBookName = ({ hidePressed }) => {
const [book, setBook] = useState();
const [bookList, setBookList] = useState(["no name"]);
const addBook = () => {
setBookList([...bookList, book]);
console.log(bookList);
};
return (
<View style={{ flexDirection: "row" }}>
<View style={{ flex: 1 }}>
<TextInput
style={{
fontSize: 20,
paddingVertical: 10,
backgroundColor: "#ececec",
paddingLeft: 10,
}}
placeholder="Enter the Book Name"
placeholderTextColor="grey"
// value={book}
onChangeText={(text) => setBook(text)}
type="text"
></TextInput>
</View>
<TouchableOpacity
style={{
flex: 0.15,
backgroundColor: "green",
alignItems: "center",
justifyContent: "center",
}}
onPress={() => addBook()}
>
<View>
<MaterialCommunityIcons name="check" size={24} color="white" />
</View>
</TouchableOpacity>
<TouchableOpacity
style={{
flex: 0.15,
backgroundColor: "red",
alignItems: "center",
justifyContent: "center",
}}
onPress={hidePressed}
>
<View>
<Entypo name="cross" size={24} color="white" />
</View>
</TouchableOpacity>
</View>
);
};
export default AddBookName;

Try to clean the previous input value after adding to the list.
const addBook = () => {
if (book) {
setBookList([...bookList, book]);
console.log(bookList);
setBook("");
}
};

This should remove duplicates:
const addBook = () => {
setBookList(Array.from(new Set([...bookList, book])));
console.log(bookList);
};
You could also store your booklist as a set instead of an array.

Related

I am trying to populate a simple list from an API Call, I can log the data but when I try to display it does not show

pretty new to react-native I am trying to fetch data to create a flat list based on the array I am populating from the data. I am able to fetch the data and log it, but when I try to display it on flatlist the data is not showing and I do not get any errors or anything. When I try to access one of the items of the array I am getting undefined. Very confused as to why its not working.
import React, {useState, useEffect} from 'react'
import { StyleSheet, Text, View, Button, FlatList, ActivityIndicator, SafeAreaViewView } from 'react-native';
export default function WorkOrderList() {
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState([]);
const [error, setError] = useState(null);
const API_ENDPOINT = 'https://randomuser.me/api/?seed=1&page=1&results=20';
useEffect(()=>{
setIsLoading(true);
fetch(API_ENDPOINT)
.then((response) => response.json())
.then(results => {
setData(results);
setIsLoading(false);
console.log(data)
})
.catch(err => {
setIsLoading(false);
setError(err);
console.log(err)
})
},[]);
if (isLoading) {
return(
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" color="#5500dc" />
</View>
)
}
if (error) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ fontSize: 18}}>
Error fetching data... Check your network connection!
{JSON.stringify(error)}
</Text>
</View>
);
}
return (
<View style={styles.container}>
<Text style={styles.text}>Favorite Contacts</Text>
<FlatList
data={data}
keyExtractor={item => item.first}
renderItem={item => (
<View style={styles.listItem}>
<Image
source={{ uri:item.picture.thumbnail}}
style={styles.coverImage}
/>
<View style={styles.metaInfo}>
<Text style={styles.title}>{`${item.name.first} ${
item.name.last
}`}</Text>
</View>
</View>
)}
/>
</View >
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8f8f8',
alignItems: 'center'
},
text: {
fontSize: 20,
color: '#101010',
marginTop: 60,
fontWeight: '700'
},
listItem: {
marginTop: 10,
paddingVertical: 20,
paddingHorizontal: 20,
backgroundColor: '#fff',
flexDirection: 'row'
},
coverImage: {
width: 100,
height: 100,
borderRadius: 8
},
metaInfo: {
marginLeft: 10
},
title: {
fontSize: 18,
width: 200,
padding: 10
}
});
Image of terminal
FlatList data Props should be the type of Array.
you need to update the setData to,
fetch(API_ENDPOINT)
.then((response) => response.json())
.then(results => {
setData(results.results); // <-- from the API data is in results object
...
After the data state is correct update the UI for FlatList as,
<FlatList
data={data}
keyExtractor={item => item.first}
renderItem={({ item }) => (
<View style={styles.listItem}>
<Image
source={{ uri:item.picture.thumbnail}}
style={styles.coverImage}
/>
<View style={styles.metaInfo}>
<Text style={styles.title}>{`${item.name.first} ${
item.name.last
}`}</Text>
</View>
</View>
)}
/>
For more details on FlatList component - https://reactnative.dev/docs/flatlist

How can I setup a multiple like system in a wishList in react Native

I'm currently working on a whishlist, I've tried two different methods.
The first one works but if I press my like ( heart icon), all the heart get fill.
The second method I can only select one article, not few.
My hook state first :
const [liked, setLiked] = useState(false)
const [counter, setCounter] =useState(-2)
const[likedProduct, setLikedProduct]=useState(false)
My code for the first point :
if(likedProduct){
var colorLike = {color: 'red'}
} else {
var colorLike = {}
}
var ArticleList = articleData.map((article, i) => {
return (<View style={{width: '42%'}}>
<TouchableOpacity
onPress={() => {
onSubmitProduct(productId)
navigation.navigate('Product')
}
}
>
<Image source={ article.img} style={{ height: 250, width: 200} } />
<View style={{ flex: 1, flexDirection: 'row', marginTop: 5, justifyContent: "space-between" }}>
<Text style={{ fontWeight: 'bold' }}>{article.brand}</Text>
<FontAwesome name="heart" size={20} style={colorLike}
onPress={() => setLikedProduct(!likedProduct)
}
/>
</View>`
The code of my second point :
<AntDesign name={liked && i== counter ? "heart":"hearto"} size={20} color="red"
onPress={()=>{
setLiked(!liked)
setCounter(i)
Any idea to get multiple like ( onPress on multiple heart) ?
Thanks
It's relatively simple to add this functionality to the app, below is the behavior of the final sample app:
import React, { useState } from 'react';
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';
import Constants from 'expo-constants';
import { FontAwesome } from '#expo/vector-icons';
const articleData = ['one', 'two', 'three', 'four', 'five'];
export default function App() {
/*
to determine if the list item is liked or not,
we will set up a state "liked", which is an array and holds
the indexes of liked list items.
*/
const [liked, setLiked] = useState([]);
/*
on TouchableOpacity we will use the below function,
it will first see if the index of cliked list item is already present,
if yes then we remove that index from "liked" state array, which is
similar to unlike functionality, and if the index is not present,
then we push that index to the "liked" state.
-----------------------------------------
onPress={() => {
console.log(liked);
if (liked.includes(index)) {
let unlike = liked.filter((elem) => elem !== index);
setLiked(unlike);
} else {
setLiked([...liked, index]);
}
}}
------------------------------------------
then comes the styling of the icon, which is pretty simple,
we just see if the index of that list item is present in the "liked" state,
if yes then it means, that item is liked and we set the color of the icon "red" else "black"
<FontAwesome
name="heart"
size={20}
😏 ➡ style={{ color: liked.includes(index) ? 'red' : 'black' }}
/>
*/
return (
<View style={styles.container}>
{articleData.map((article, index) => (
<TouchableOpacity
onPress={() => {
console.log(liked);
if (liked.includes(index)) {
let unlike = liked.filter((elem) => elem !== index);
setLiked(unlike);
} else {
setLiked([...liked, index]);
}
}}>
<View style={styles.list}>
<Text>{article}</Text>
<FontAwesome
name="heart"
size={20}
style={{ color: liked.includes(index) ? 'red' : 'black' }}
/>
</View>
</TouchableOpacity>
))}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
list: {
padding: 10,
margin: 5,
flexDirection: 'row',
flex: 1,
justifyContent: 'space-between',
backgroundColor: 'white',
},
});
Working App : Expo Snack

React Native TouchableOpacity onPress using index/key of the item in FlatList

We created a FlatList component and a Contact screen in our app. We want to add 'heart' icon to the near of the images in Contact screen. We added heart icon near to all of items. But, if we pressed one of these icons, it changes all of theirs colors to red, not only one of them. We want to change the color of clicked item.
Screenshot of our program:
This is our FlatList component:
import React, { Component } from 'react';
import { View, Text, SafeAreaView, StyleSheet, FlatList, Image, TouchableOpacity,
TouchableWithoutFeedback, TextInput } from 'react-native';
import { Right, Icon } from 'native-base';
import data from '../../data';
export default class FlatListComponent extends Component {
state = {
text: '',
contacts: data,
like: false,
color: 'white',
}
toggleLike=()=>{
this.setState({
like: !this.state.like
})
if(this.state.like){
this.setState({
color: 'red',
})
}else{
this.setState({
color: 'white',
})
}
}
renderContactsItem = ({item, index}) => {
return (
<View style={[styles.itemContainer]}>
<Image
style={styles.avatar}
source={{ uri: item.image }} />
<View style={styles.textContainer}>
<Text style={[styles.name], {color: '#fafafa'}}>{item.first_name}</Text>
<Text style={{ color: '#fafafa' }}>{item.last_name}</Text>
</View>
<Right style={{justifyContent: 'center'}}>
<TouchableWithoutFeedback onPress={this.toggleLike}>
{/*{this.like ? (
<Icon name="heart" type='FontAwesome' style={{paddingRight: 10, fontSize: 30, color: 'red'}} />
) :
( <Icon name="heart" type='FontAwesome' style={{paddingRight: 10, fontSize: 30, color: 'white'}} /> )
}*/}
<Icon name='heart' type='FontAwesome' size={32} style={{color: this.state.color === "white" ? 'white' :'red', paddingRight: 10 }}/>
{/*<Icon name="heart" type='FontAwesome' style={{paddingRight: 10, fontSize: 30, color: this.state.color}} />*/}
</TouchableWithoutFeedback>
</Right>
</View>
);
}
searchFilter = text => {
const newData = data.filter(item => {
const listItems = `${item.first_name.toLowerCase()}`
return listItems.indexOf(text.toLowerCase()) > -1;
});
this.setState({
contacts: newData,
});
};
renderHeader = () => {
const {text} = this.state;
return (
<View style={styles.searchContainer}>
<TextInput
onChangeText = {text => {
this.setState ({
text,
});
this.searchFilter(text);
}}
value={text}
placeholder="Search..."
style={styles.searchInput} />
</View>
)
}
render() {
return (
<FlatList
ListHeaderComponent={this.renderHeader()}
renderItem={this.renderContactsItem}
keyExtractor={item => item.id}
data={this.state.contacts}
/>
);
}
}
const styles = StyleSheet.create({
itemContainer: {
flex: 1,
flexDirection: 'row',
paddingVertical: 10,
borderBottomWidth: 1,
borderBottomColor: '#eee'
},
avatar: {
width: 50,
height: 50,
borderRadius: 25,
marginHorizontal: 10,
},
textContainer: {
justifyContent: 'space-around',
},
name: {
fontSize: 16,
},
searchContainer: {
padding: 10
},
searchInput: {
fontSize: 16,
backgroundColor: '#f9f9f9',
padding: 10,
}
});
Our Contact screen is just:
import React from 'react';
import 'SafeAreaView' from 'react-native';
import FlatList from './FlatList';
export default function Contact() {
<SafeAreaView style={{ flex: 1 }}>
<FlatList />
</SafeAreaView>
}
How can we implement this?
I've run into this recently :) One option is to make the renderContactsItem its own component. For example:
const RenderContactsItem = ({item, index}) => {
const [like, setLike] = useState(false);
const [color, setColor] = useState("white");
const toggleLike = () => {
setLike(!like)
if(like) {
setColor("red");
} else {
setColor("white");
}
}
return (
<View style={[styles.itemContainer]}>
<Image
style={styles.avatar}
source={{ uri: item.image }} />
<View style={styles.textContainer}>
<Text style={[styles.name], {color: '#fafafa'}}>{item.first_name}</Text>
<Text style={{ color: '#fafafa' }}>{item.last_name}</Text>
</View>
<Right style={{justifyContent: 'center'}}>
<TouchableWithoutFeedback onPress={toggleLike}>
<Icon name='heart' type='FontAwesome' size={32} style={{color, paddingRight: 10 }}/>
</TouchableWithoutFeedback>
</Right>
</View>
);
}
In this case, each item manages its own state, so setting like doesn't set it for every item.
Another option would be to build an object with "like" states and set the values as the hearts are pressed. For example:
state = {
text: '',
contacts: data,
like: {},
color: 'white', // You don't need this
}
Then, when a heart is pressed, you can send toggleLike the index, and set the state like so:
toggleLike = (index) => {
let newLike = {...this.state.like};
newLike[index] = !Boolean(newLike[index]);
this.setState({
like: newLike,
});
}
And render the color conditionally depending on the index value of the like state, like so:
<Icon name='heart' type='FontAwesome' size={32} style={{color: this.state.like[index] ? 'red' :'white', paddingRight: 10 }}/>

Sending data to another component with (React Native Navigation)

I want to send basic data from Home.js component to details.js component.
My home.js
import React, { useState, useEffect } from 'react'
import { View, TouchableHighlight, Image, FlatList, ActivityIndicator, Text, StyleSheet } from 'react-native'
function HomeScreen({ navigation }) {
const [isLoading, setIsLoading] = useState(true)
const [dataSource, setDataSource] = useState([])
useEffect(() => {
fetch('http://www.json-generator.com/api/json/get/bVxGaxSSgi?indent=2')
.then((response) => response.json())
.then((responseJson) => {
setIsLoading(false)
setDataSource(responseJson)
})
.catch((error) => {
alert(error)
});
})
if (isLoading) {
return (
<View style={{ flex: 1, padding: 20 }}>
<ActivityIndicator />
</View>
)
}
return (
<View style={{ flex: 1, paddingTop: 20, paddingHorizontal: 20 }}>
<FlatList
data={dataSource}
renderItem={({ item }) =>
<TouchableHighlight onPress={() => navigation.navigate('Details',{text:'alperen'})} underlayColor="white">
<View style={styles.button}>
<View style={styles.div}>
<View style={styles.inlineDiv}>
<Image source={{ uri: 'https://source.unsplash.com/random/900&#x2715700/?fruit' }} style={styles.pic} />
<Text>{item.name}, {item.about}</Text>
</View>
</View>
</View>
</TouchableHighlight>
}
keyExtractor={({ _id }, index) => _id}
/>
</View>
)
}
export default HomeScreen
const styles = StyleSheet.create({
pic: {
width: '100%',
height: 200,
borderRadius: 20,
},
div: {
shadowColor: "#fff",
shadowOffset: {
width: 0,
height: 1,
},
shadowOpacity: 0.20,
shadowRadius: 50,
elevation: 0,
marginBottom: 10
},
inlineDiv: {
padding: 5,
},
button: {
alignItems: 'center',
},
});
This is my details.js component
import React, { useEffect } from 'react'
import { View, Text } from 'react-native'
function DetailsScreen( navigation ) {
useEffect(() => {
alert(navigation.text)
})
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text></Text>
</View>
);
}
export default DetailsScreen
I want to go to the other page and at the same time I want to send and print the data. This code is currently returning undefined. How Can I send data ? Probably I can use props but I don't know usage.
Change
function DetailsScreen( navigation ) {
useEffect(() => {
alert(navigation.text)
})
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text></Text>
</View>
);
}
to
function DetailsScreen({navigation}) {
useEffect(() => {
alert(navigation.state.params.text)
})
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text></Text>
</View>
);
}
And try
In first screen
<TouchableHighlight onPress={() => navigation.navigate('Details',{item})} underlayColor="white">
In next screen you can get data
this.props.navigation.state.params.item
Example:
<Image source={this.props.navigation.state.params.item.img}/>
<Text>{this.props.navigation.state.params.item.text}</Text>
Sending data operation is success but details page is not working. I looked 'react-navigation documentation ' (https://reactnavigation.org/docs/en/params.html). There is a keyword ('route') we have to use it.
function DetailsScreen({ route }) {
const { text } = route.params;
useEffect(() => {
var a = JSON.stringify(text)
alert(a)
})
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text></Text>
</View>
);
}
I tried this code and its working success.

A problem with a trivial react native code with hooks

Update:
Problem 2. is solved (thanks to #NikolayNovikov reply) by replacing "setResources({ resources: response.data })" with
"setResources(response.data)".
Problem 1. I cannot reproduce it...
Description of the problem:
The following trivial example is fetching data from jsonplaceholder.typicode.com, and, since there are 100 'posts' and 200 'todos', it is supposed to display 100 and 200 when you click on these buttons.
For some reason that I cannot find (embarrassing, I know...), there are two problems:
When I click on the 'posts' button, useEffect is called twice (once with 'todos' and once with 'posts', and when I click on the 'todos' button it is not called at all.
resource.length is not rendered
Any idea what is wrong?
App.js:
import React, { useState } from 'react';
import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';
import { ResourceList } from './components/ResourceList';
console.warn('In App.js: Disabling yellowbox warning in genymotion');
console.disableYellowBox = true;
export const App = () => {
const [resource, setResource] = useState('todos');
return (
<View style={{ flex: 1, alignItems: 'center', marginTop: 100 }}>
<Text style={{ fontSize: 20, fontWeight: '500' }}>
The Application has been loaded!
</Text>
<View style={{ flexDirection: 'row', marginTop: 100,
alignItems: 'center', justifyContent: 'center' }}>
<TouchableOpacity onPress={() => setResource('posts')}>
<View style={styles.button}>
<Text style={styles.buttonText}>
Posts
</Text>
</View>
</TouchableOpacity>
<View style={{ width: 20 }} />
<TouchableOpacity onPress={() => setResource('todos')}>
<View style={styles.button}>
<Text style={styles.buttonText}>
Todos
</Text>
</View>
</TouchableOpacity>
</View>
<ResourceList resource={resource} />
</View>
);
}
const styles = StyleSheet.create({
buttonText: {
color: 'black',
fontSize: 20
},
button: {
backgroundColor: '#a8a',
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 5,
paddingHorizontal: 10,
borderRadius: 2
}
});
ResourceList.js:
import React, { useState, useEffect } from 'react';
import { View, Text } from 'react-native';
import axios from 'axios';
// export const ResourceList = (props) => {
export const ResourceList = ({ resource }) => { // restructured props
const [resources, setResources ] = useState([]);
const fetchResource = async(resource) => {
console.log('+++ ResourceList/fetchResource calling axios. path:',
`https://jsonplaceholder.typicode.com/${resource}`);
const response = await axios.get(`https://jsonplaceholder.typicode.com/${resource}`);
console.log(response);
setResources({ resources: response.data })
}
useEffect(() => {
console.log('--- ResourceList/useEffect. resource: ', resource);
fetchResource(resource);
}, [resource]);
return (
<View style={{ flex: 1, alignItems: 'center', marginTop: 100 }}>
<Text style={{ fontSize: 20, fontWeight: '500' }}>
{resources.length}
</Text>
</View>
);
}
In you ResourceList.js component setResources should be different, please try this:
setResources(response.data)