I followed this tutorial in order to have a slider with images : https://www.youtube.com/watch?v=HAgJqaJc-ck
I have three main files :
Carousel.js
const Carousel = ({data}) => {
const scrollX = new Animated.Value(0)
let position = Animated.divide(scrollX, width)
if (data && data.length){
return (
<View>
<FlatList data = {data}
keyExtractor= {(item, index) => 'key' + index}
horizontal
pagingEnabled
scrollEnabled
snapToAlignment = 'center'
scrollEventThrottle = {16}
decelerationRate = {"fast"}
showsHorizontalScrollIndicator = {false}
renderItem = {({item}) => {
return <CarouselItem item= {item} navigation={props.navigation}/>
}}
onScroll = {Animated.event(
[{nativeEvent : {contentOffset: { x : scrollX}}}]
)}
/>
<View style = {styles.dotView}>
{data.map((_, i) => {
let opacity = position.interpolate({
inputRange : [i - 1, i, i + 1],
outputRange: [0.3, 1, 0.3],
extrapolate : 'clamp'
})
return <Animated.View
key={i}
style = {{opacity, height : 10, width : 10, background: '#595959', margin: 8 , borderRadius: 5}}
/>
})}
</View>
</View>
)
}
console.log('Please provide Images')
return null
}
CarouselItem.js
const CarouselItem = ({item, navigation}) => {
const url = item.url
return (
<View style={styles.cardView} >
<TouchableOpacity onPress={() => props.navigation.navigate("BoatInfo")}><Image style={styles.image} source={{uri: item.url}} /></TouchableOpacity>
<View style={styles.textView}>
<Text style={styles.itemTitle}>{item.title}</Text>
<Text style={styles.imageDescription}>{item.description}</Text>
</View>
</View>
)
}
And Data.js
export const dummyData = [
{
title: 'BOAT DETAILS', url : require('../component/schema.jpg'),
description: 'PDF containing the specificities of the boat', id : 1
},
{
title: 'TUTORIALS', url : require('../component/tuto.png'),
description: 'Become a capitain by watching these videos', id : 2
},
{
title: 'YOUR TRIP', url : require('../component/trip.png'),
description: 'Follow your trip details', id : 3
},
]
Now, As you see in CarouselItem, I am trying to add a functionality so that when I press on the image, it would take me to another page (each picture takes me to a different page). However, when I try to do so, I get the following error:
So, I understand that in props it is empty...
But when I switch const CarouselItem = ({ item }, props) => { to const CarouselItem = ( props, { item }) => { Then I get the following error:
What I don't understand is that when I remove {item} then the navigation works and when I remove props then {item} works... How can both work? What am I doing wrong ?
Only screens in React Native automatically have access to the navigation prop, so you'll need to pass it to the CarouselItem component, then use the always fun object destructuring to get your particular props in the component (like you did with item). It should look like this:
...
renderItem = {({item}) => {
return <CarouselItem item={item} navigation={navigation}/>
}}
...
and then:
const CarouselItem = ({ item, navigation }) => {
...
and that should work!
Good luck!
You have to pass navigation prop object from flatlist with item object too something like this
navigate ={props.navigation} item={item}
in crousal function you have to write like this
crousalItem =(item, navigation) =>
This way you got both item and navigation object.
use item to list data, navigation.navigate() to move to another screen
Related
I am using React native and
I have a loop in which some property types are being displayed,
i created only one state for it because property types are being fetched from DB.
What i want to do is to show them selected. kindly show me a way to achieve it.
const [propBCol, setPropBCol] = useState('#EDEDEE');
const [propTCol, setPropTCol] = useState('#000000');
{propType.map((item, index) => {
return (
<TouchableOpacity style={{
backgroundColor: propBCol,
...ListingFilterStyles.filterAnyBtn,
...ListingFilterStyles.btnMale,
}}
onPress={() => proptypes(item)}>
<Text style={{color: propTCol}}>{item.value}</Text>
</TouchableOpacity>
);
})}
the propTypes function:
const proptypes = item => {
setPropBCol('red');
setPropTCol('White')
}
enter code here
How can i change the selected item color, by above code all are item colors are being changed
const [propBCol, setPropBCol] = useState('#EDEDEE');
const [propTCol, setPropTCol] = useState('#000000');
const [selected, setSelected] = useState([]);
{propType.map((item, index) => {
return (
<TouchableOpacity style={{
backgroundColor: selected.includes(index) ? propBCol : item.propBCol,
...ListingFilterStyles.filterAnyBtn,
...ListingFilterStyles.btnMale,
}}
onPress={() => proptypes(index)}>
<Text style={{color: selected.includes(index) ? propTCol : item.propTCol}}>{item.value}</Text>
</TouchableOpacity>
);
})}
const proptypes = (index) => {
setSelected(prev => {
return [
...prev,
index
]
})
}
You should not declare a state for each one of the propType you are receiving. What you can do is, you can add a key inside the object that you are getting and saving it inside the state.
And on press you can change the value of the checked key using simple index values of the array. You have to pass the data inside the method and write the method as below
const propsTypes = (item, index) => {
var propTypesVar = [...propTypes]
propTypesVar[index].propBCol= <<Your desired color>>
propTypesVar[index].propTCol= <<Your desired color>>
setPropType(propTypesVar)
}
After that the value of propBCol will be inside the object of your propType array and you can render anything to show the user if the value is selected or not i.e.
{propType.map((item, index) => {
return (
<TouchableOpacity
style={{
backgroundColor: item.propBCol,
...ListingFilterStyles.filterAnyBtn,
...ListingFilterStyles.btnMale,
}}
onPress={() => proptypes(item)}>
<Text style={{color: item.propTCol}}>{item.value}</Text>
</TouchableOpacity>
);
})}
I have a list of category from calling external API and I am displaying it in a flatlist like this in horizontal scroll
but it is not changing state onPress , I want it should change color when user click on particular tab/button
my API json format data is like this
const data = [
{
id: '1',
title: 'General'
},
{
id: '2',
title: 'Student-Visa'
},
{
id: '3',
title: 'Study'
},
{
id: '4',
title: 'Festival'
},
{
id: '5',
title: 'NorthIndian-Food'
},
]
and I am using it as
const renderItem = ({ item, index }) => (
<View key={index} style={selectedCategory === item.title ? styles.activeCategory : styles.categoryContainer}>
<TouchableOpacity
activeOpacity={0.6}
underlayColor={COLORS.white}
onPress={()=>handleCategory(item.title)}
>
<Text>{item.title}</Text>
</TouchableOpacity>
</View>
);
return (
<List
data={data}
renderItem={renderItem}
horizontal={true}
style={styles.container}
contentContainerStyle={styles.contentContainer}
showsHorizontalScrollIndicator={false}
keyExtractor={item => item.id.toString()}
/>
)
const handleSelectedCategory = (title) => {
setSelectedCategory(title);
console.log(selectedCategory);
}
const [selectedCategory, setSelectedCategory] = useState();
I am using it as a separate component in another component
<FAQcategory selectedCategory={selectedCategory} />
any suggestion ? or help what I am doing wrong
Thanks in advance
This might help
const [selectedCategory, setSelectedCategory] = useState(0); // should be item index
const handleCategory = (index) => {
setSelectedCategory(index);
};
const renderItem = ({ item, index }) => (
<View
key={index}
style={
selectedCategory === index
? styles.activeCategory
: styles.categoryContainer
}
>
<TouchableOpacity
....
onPress={() => handleCategory(index)} // send index in param
>
....
</TouchableOpacity>
</View>
);
After getting data from API I set it to state, and render items in Flatlist,
when I select any item from it I manipulate data and add a new property to item object named as "toggle: true"
and it's works well when I select any item from list I add a border based on toggle,
But when I go back to previous screen then re open the lists screen I can see the border rendered around the items, although I reset the state when the unmounted screen
So what's the wrong I made here?
Code snippet
Data
export default {
...
services: [
{
id: 0,
name: 'nameS0',
logo:
'https://cdn2.iconfinder.com/data/icons/hotel-98/64/hair-dryer-tools-beauty-hairdressing-512.png',
price: 19.99,
},
],
employees: [
{
id: 0,
name: 'name0',
img:
'https://www.visualelementmedia.com/wp-content/uploads/2015/04/person-4-400x629.jpg',
},
...
],
};
const VendorProfile = ({navigation}) => {
const [services, setServices] = React.useState(null);
const [employees, setEmployees] = React.useState(null);
const [serviceSelected, setServiceSelected] = React.useState(null);
const [employeeSelected, setEmployeeSelected] = React.useState(null);
// For selected Item (services, employees)
const itemSelected = (data, id) => {
const updated = data.map((item) => {
item.toggle = false;
if (item.id === id) {
item.toggle = true;
data === services
? setServiceSelected(item)
: setEmployeeSelected(item);
}
return item;
});
data === services ? setServices(updated) : setEmployees(updated);
};
...
const renderEmployees = ({item}) => {
return (
<TouchableOpacity
onPress={() => itemSelected(employees, item.id)}
delayPressIn={0}
style={styles.employeeContainer}>
<EmployeePattern style={{alignSelf: 'center'}} />
<View style={styles.employeeLogo}>
<Image
source={{uri: item.img}}
style={[styles.imgStyle, {borderRadius: 25}]}
/>
</View>
<View style={{marginTop: 30}}>
<Text style={{textAlign: 'center'}}> {item.name}</Text>
</View>
<View style={{marginTop: 10, alignSelf: 'center'}}>
{item.toggle && <AntDesign name="check" size={25} color="#000" />} // here it's stuck after back and reopen the screen
</View>
</TouchableOpacity>
);
};
React.useEffect(() => {
setServices(VendorProfileData.services);
setEmployees(VendorProfileData.employees);
() => {
setServices(null);
setEmployees(null);
};
}, []);
return (
<View style={styles.container}>
<FlatList
data={services}
renderItem={renderServices}
horizontal
keyExtractor={(item) => item.id.toString()}
contentContainerStyle={{
justifyContent: 'space-between',
flexGrow: 1,
}}
/>
.....
</View>
);
};
Ok so after trying multiple times, i got it
change this
const updated = data.map((item) => {
to this
const updated = data.map((old) => {
let item = {...old};
and please make sure everything is working and we didn't break a thing :),
On your ItemSelected function you are passing the whole employees list, and going through it now thats fine, but when you changing one item inside this list without "recreating it" the reference to that item is still the same "because its an object" meaning that we are modifying the original item, and since we are doing so, the item keeps its old reference, best way to avoid that is to recreate the object,
hope this gives you an idea.
I am creating an app that allows a user to select multiple items from a Flatlist and the state changes once selected. However when I move away from that screen, all the selected items go back to the unselected state. How can use Firebase to save that state so it doesn't revert when I leave the screen? I am also open to alternative solutions to this issue.
Thank you for your time.
export default function StoreCatalogue({ navigation }) {
const { data, status } = useQuery('products', fetchProducts)
const [catalogueArray, setCatalogueArray] = useState([])
const [isSelected, setIsSelected] = useState([])
const [addCompare, setAddCompare] = useState(false)
const storeToDB = async (item) => {
if (!addCompare) {
await db.collection('users').doc(auth.currentUser.uid).collection('myProducts').doc(item.storeName + item.genName).set({
product_id: item.id,
product_genName: item.genName
})
} else {
await db.collection('users').doc(auth.currentUser.uid).collection('myProducts').doc(item.storeName + item.genName).delete()
}
}
const clickHandler = async (item) => {
setAddCompare(
addCompare ? false : true
)
if (isSelected.indexOf(item) > -1) {
let array = isSelected.filter(indexObj => {
if (indexObj == item) {
return false
}
return true
})
setIsSelected(array)
} else {
setIsSelected([
...isSelected, item
])
}
}
return (
<View style={styles.container}>
<FlatList
extraData={isSelected}
keyExtractor={(item) => item.id}
data={catalogueArray}
renderItem={({ item }) => (
<TouchableOpacity style={styles.btn} onPress={() => { storeToDB(item); clickHandler(item) }}>
<MaterialCommunityIcons name='plus-circle-outline' size={24} color={isSelected.indexOf(item) > -1 ? 'grey' : 'green'} />
<View style={{ position: 'absolute', bottom: 3 }}>
<Text style={{ fontSize: 10, textAlign: 'center', color: isSelected.indexOf(item) > -1 ? 'grey' : 'green' }}>{isSelected.indexOf(item) > -1 ? 'item \nAdded' : 'Add to\n Compare '}</Text>
</View>
</TouchableOpacity>
)}
/>
</View>
)
}
The problem is when you leave a screen the state gets reset to original, you can use Redux to store the state separately and on an event store the information on firebase once.
Firebase can be costly for this operation since every read and write will be chargeable.
I'm trying to center an item within a horizontal listView when selected. My current strategy is to first measure the item and scroll to the x coordinates of the referenced item within the view.
Currently, any time I press an item ListView scrolls to the very end x: 538
Is there an easier way to implement this while keeping the code stateless / functional?
const ItemScroll = (props) => {
const createItem = (obj, rowID) => {
const isCurrentlySelected = obj.id === props.currentSelectedID
function scrollToItem(_scrollViewItem, _scrollView) {
// measures the item coordinates
_scrollViewItem.measure((fx) => {
console.log('measured fx: ', fx)
const itemFX = fx;
// scrolls to coordinates
return _scrollView.scrollTo({ x: itemFX });
});
}
return (
<TouchableHighlight
ref={(scrollViewItem) => { _scrollViewItem = scrollViewItem; }}
isCurrentlySelected={isCurrentlySelected}
style={isCurrentlySelected ? styles.selectedItemContainerStyle : styles.itemContainerStyle}
key={rowID}
onPress={() => { scrollToItem( _scrollViewItem, _scrollView); props.onEventFilterPress(obj.id, rowID) }}>
<Text style={isCurrentlySelected ? styles.selectedItemStyle : styles.itemStyle} >
{obj.title}
</Text>
</TouchableHighlight>
)
};
return (
<View>
<ScrollView
ref={(scrollView) => { _scrollView = scrollView; }}
horizontal>
{props.itemList.map(createItem)}
{props.onItemPress}
</ScrollView>
</View>
);
};
Update
With #Ludovic suggestions I have now switch to FlatList, I am not sure how trigger scrollToIndex with a functional component. Below is my new ItemScroll
const ItemScroll = (props) => {
const {
itemList,
currentSelectedItem
onItemPress } = props
const renderItem = ({item, data}) => {
const isCurrentlySelected = item.id === currentSelectedItem
const _scrollToIndex = () => { return { viewPosition: 0.5, index: data.indexOf({item}) } }
return (
<TouchableHighlight
// Below is where i need to run onItemPress in the parent
// and scrollToIndex in this child.
onPress={[() => onItemFilterPress(item.id), scrollToIndex(_scrollToIndex)]} >
<Text style={isCurrentlySelected ? { color: 'red' } : { color: 'blue' }} >
{item.title}
</Text>
</TouchableHighlight>
)
}
return (
<FlatList
showsHorizontalScrollIndicator={false}
data={itemList}
keyExtractor={(item) => item.id}
getItemLayout={(data, index) => (
// Max 5 items visibles at once
{ length: Dimensions.get('window').width / 5, offset: Dimensions.get('window').width / 5 * index, index }
)}
horizontal
// Here is the magic : snap to the center of an item
snapToAlignment={'center'}
// Defines here the interval between to item (basically the width of an item with margins)
snapToInterval={Dimensions.get('window').width / 5}
renderItem={({item, data}) => renderItem({item, data})} />
);
};
In my opinion, you should use FlatList
FlatList have a method scrollToIndex that allows to directly go to an item of your datas. It's almost the same as a ScrollView but smarter. Sadly the documentation is very poor.
Here is an example of a FlatList I did
let datas = [{key: 0, text: "Hello"}, key: 1, text: "World"}]
<FlatList
// Do something when animation ended
onMomentumScrollEnd={(e) => this.onScrollEnd(e)}
ref="flatlist"
showsHorizontalScrollIndicator={false}
data={this.state.datas}
keyExtractor={(item) => item.key}
getItemLayout={(data, index) => (
// Max 5 items visibles at once
{length: Dimensions.get('window').width / 5, offset: Dimensions.get('window').width / 5 * index, index}
)}
horizontal={true}
// Here is the magic : snap to the center of an item
snapToAlignment={'center'}
// Defines here the interval between to item (basically the width of an item with margins)
snapToInterval={Dimensions.get('window').width / 5}
style={styles.scroll}
renderItem={ ({item}) =>
<TouchableOpacity
onPress={() => this.scrollToIndex(/* scroll to that item */)}
style={styles.cell}>
<Text>{item.text}</Text>
</TouchableOpacity>
}
/>
More about FlatList : https://facebook.github.io/react-native/docs/flatlist#__docusaurus
viewPosition prop will center your item.
flatListRef.scrollToIndex({ index: index, animated: true, viewPosition: 0.5 })
flatlistRef.current.scrollToIndex({
animated: true,
index,
viewOffset: Dimensions.get('window').width / 2.5,
});
You can try to put viewOffset inside the scrollToIndex property, it will handle your component offset, I use 2.5 to make it in middle of screen , because I show 5 item each section and one of focused item will be in middle of screen