Get position of individual Items in flatlist - react-native

I have flatlist horizontal like below
const DATA = [
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
title: 'First Item',
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
title: 'Second Item',
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d72',
title: 'Third Item',
},
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad353abb28ba',
title: 'Fourth Item',
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd291aa97f63',
title: 'Fifth Item',
},
{
id: '58694a0f-3da1-471f-bd961-145571e29d72',
title: 'Sixth Item',
},
];
const Item = ({ title }) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const App = () => {
const renderItem = ({ item }) => (
<Item title={item.title} />
);
return (
<SafeAreaView style={styles.container}>
<FlatList
horizontal
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</SafeAreaView>
);
}
Whenever Item entered the viewport , I want to add animation to that element.I can get X and Y position of scroll with onScroll , now how do i get the positions of items to check if its in view port or if it went away from viewport...
Thank you.

Sorry for the late response. My pc has been super weird lately so when I encounter errors I have to second guess myself, and when nothing appears wrong, I second guess my pc (this time it was entirely me).
Here's my answer. I implemented the basic fade in/out [animation example][1] into the Item component. Whether it fades out or in is decided by the prop isViewable
// Item.js
const Item = (props) => {
const {
item:{title, isViewable}
} = props
/*
I copied and pasted the basic animation example from the react-native dev page
*/
const fadeAnim = useRef(new Animated.Value(1)).current;
const fadeIn = () => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 1000,
useNativeDriver:false
}).start();
};
const fadeOut = () => {
Animated.timing(fadeAnim, {
toValue: 0,
duration: 1500,
useNativeDriver:false
}).start();
};
/* end of animation example*/
// fade in/out base on if isViewable
if(isViewable || isViewable == 0)
fadeIn()
else
fadeOut()
const animation = {opacity:fadeAnim}
return (
//add animation to Animated.View
<Animated.View style={[style.itemContainer,animation]}>
<View style={style.item}>
<Text style={style.title}>{title}</Text>
</View>
</Animated.View>
);
}
Create a FlatListWrapper (to avoid the onViewableItemChange on fly error). By doing this, as long as you don't make changes to FlatListWrapper, you wont get the on the fly error
// FlatListWrapper.js
const FlatListWrapper = (props) => {
// useRef to avoid onViewableItemsChange on fly error
const viewabilityConfig = useRef({
// useRef to try to counter the view rerender thing
itemVisiblePercentThreshold:80
}).current;
// wrapped handleViewChange in useCallback to try to handle the onViewableItemsChange on fly error
const onViewChange = useCallback(props.onViewableItemsChanged,[])
return (
<View style={style.flatlistContainer}>
<FlatList
{...props}
horizontal={true}
onViewableItemsChanged={onViewChange}
/>
</View>
);
}
const style = StyleSheet.create({
flatlistContainer:{
borderWidth:1,
borderColor:'red',
width:'50%',
height:40
},
// main FlatList component
const FlatListAnimation = () => {
// store the indices of the viewableItmes
const [ viewableItemsIndices, setViewableItemsIndices ] = useState([]);
return (
<SafeAreaView style={style.container}>
<FlatListWrapper
horizontal={true}
//{/*give each data item an isViewable prop*/}
data={DATA.map((item,i)=>{
item.isViewable=viewableItemsIndices.find(ix=>ix == i)
return item
})}
renderItem={item=><Item {...item}/>}
keyExtractor={item => item.id}
onViewableItemsChanged={({viewableItems, changed})=>{
// set viewableItemIndices to the indices when view change
setViewableItemsIndices(viewableItems.map(item=>item.index))
}}
//{/*config that decides when an item is viewable*/}
viewabilityConfig={{itemVisiblePercentThreshold:80}}
extraData={viewableItemsIndices}
/>
{/* Extra stuff that just tells you what items should be visible*/}
<Text>Items that should be visible:</Text>
{viewableItemsIndices.map(i=><Text> {DATA[i].title}</Text>)}
</SafeAreaView>
);
}
const style = StyleSheet.create({
container:{
padding:10,
alignItems:'center'
},
flatlistContainer:{
borderWidth:1,
borderColor:'red',
width:'50%',
height:40
},
item:{
borderWidth:1,
padding:5,
},
itemContainer:{
padding:5,
}
})
By wrapping your FlatList in a separate file, you wont encounter the "onViewableItemsChange on the fly" error as long as you dont modify FlatListWrapper.js
[1]: https://reactnative.dev/docs/animated

Use onViewableItemsChanged this is called when the items in the flatlist changes.
const handleViewableItemsChanged = (viewableItems, changed) => {}
<Flatlist
...
onViewableItemsChanged={handleViewableItemsChanged}

Related

How to pass data from a FlatList to a BottomSheet in React Native?

I used BottomSheet from https://gorhom.github.io/react-native-bottom-sheet/
Say I have a FlatList that renders buttons, how can I make it that when a certain button is pressed, the BottomSheet will open and will contain whatever data I passed in it? Say, the title data.
Here's my code:
import { BottomSheetModal, BottomSheetModalProvider, BottomSheetScrollView } from '#gorhom/bottom-sheet';
import { useRef, useState, useCallback, useMemo } from "react"
const DATA = [
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
title: 'John Doe',
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
title: 'John Smith',
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d72',
title: 'John Lennon',
},
];
const Item = ({ title }) => (
<AppButton title={title} style={{ marginVertical: 10 }} />
);
const App = () => {
// ref
const bottomSheetModalRef = useRef(null);
// variables
const snapPoints = useMemo(() => ['25%', '50%'], []);
// callbacks
const handlePresentModalPress = useCallback(() => {
bottomSheetModalRef.current?.present();
}, []);
const handleSheetChanges = useCallback((index) => {
console.log('handleSheetChanges', index);
}, []);
return (
<View style={styles.container}>
<FlatList
data={DATA}
keyExtractor={item => item.id}
renderItem={({ item }) => <Item title={item.title} />}
/>
</View>
<BottomSheetModalProvider>
<BottomSheetModal
ref={bottomSheetModalRef}
index={0}
snapPoints={snapPoints}
enablePanDownToClose
onChange={handleSheetChanges}
>
<BottomSheetScrollView>
</BottomSheetScrollView>
</BottomSheetModal>
</BottomSheetModalProvider>
)
}
I only copied much of the code for the BottomSheet from the docs and I have no knowledge yet about usecallback and useref so if what I want to happen needs those two, kindly indicate it :)
If im not using a list I managed to do it but had to create many copies of bottom sheet and refs and i know that is not very efficient
Refer to the screenshot below

Refresh FlatList React

the flatList after deleting work but not displayed on Screen items does't change
const App = () => {
const data = [
{ id: 1, text: "item One" },
{ id: 2, text: "item Two" },
{ id: 3, text: "item Three" },
{ id: 4, text: "item Four" },
];
const [state, setState] = useState(data);
const onPressItem = (id) => {
// code de delete item
const data = state;
alert(id);
const filterrArray = data.filter((val, i) => {
if (val.id !== id) {
return val;
}
});
console.log("filteerArray " , filterrArray)
setState( filterrArray)
};
const renderItem = ({ item }) => {
return (
<TouchableOpacity style={styles.item}>
<Text style={styles.text}>{item.text}</Text>
<Button title="Supprimer" onPress={() => onPressItem(item.id)} />
</TouchableOpacity>
);
};
const handleCheck = () => {
console.log('__________________________')
console.log(state)
}
return (
<View style={{ flex: 1, padding: 0, marginTop: "10%" }}>
<SafeAreaView>
<FlatList
data={data}
keyExtractor={(item) => item.id}
renderItem={renderItem}
/>
</SafeAreaView>
<Button
title = "Verifier state"
onPress={handleCheck}
/>
</View>
);
};
i try the console log every time before the deleting and after the deleting is work fine but doest change on Screen or refresh automatically i dont know
not sure if I got your question right but if you want to update your flatlist, you need to pass state to data.
data={state}

How to count a persentage of a painted area of the screen?

My question in one picture
There is a screen in my project where you able to draw on the screen; the goal of the screen - to draw over full screen in time. It was pretty hard to get paint part to work. but now its even harder - i dont know how to count the percent of painted area of the screen and show the result on the screen, at the same time changing it while painting on the screen.
I believe the right way to do so - to initialize a "var" and somehow make it depend on the area painted... But i still dont get the right way to do smth like this.
My project consists of two parts - absolute and block section. I will attach the parts of the both sections, so you can see how the painting part is done and so counting part (not done).
The absolute part is (do not included css);
type PropsType = {
blocksCount: number;
selectedIndexes: Array<number>;
setSelectedIndexes: (newSelectedIndexes: Array<number>) => void;
};
const AbsoluteSection: React.FC<PropsType> = (props) => {
const [counter, setCounter] = useState(60);
const navigation = useNavigation();
const percent = Math.round(
(props.selectedIndexes.length / props.blocksCount) * 100,
);
counter > 0 && setTimeout(() => setCounter(counter - 1), 1000);
const navigate = (isFail?: boolean) => {
navigation.navigate('TestResultScreen', {
title: 'Мультитач',
isSuccess: isFail,
});
props.setSelectedIndexes([]);
setCounter(60);
};
useEffect(() => {
counter <= 0 && navigate(true);
}, [counter]);
useEffect(() => {
if (props.selectedIndexes.length >= props.blocksCount) {
navigate();
}
}, [props.selectedIndexes]);
return (
<View style={styles.absolute_wrap} pointerEvents={'none'}>
<Text size={24} color={'#AAAAAA'} isCenterAlign>
{props.selectedIndexes.length <= 0 &&
'Починай замальовувати пальцем екран'}
</Text>
<View style={styles.pie_wrap}>
<Pie
radius={80}
innerRadius={75}
sections={[
{
percentage: percent,
color: '#FFC107',
},
]}
backgroundColor="#ddd"
/>
<View style={styles.gauge}>
<Text style={styles.gaugeText} size={28}>
{percent}%
</Text>
</View>
</View>
<Text size={28}>{counter} сек</Text>
</View>
);
};
The block part (more to painting) is:
type PropsType = {
isSelected: boolean;
onPress: () => void;
};
const BlockItem: React.FC<PropsType> = (props) => {
return (
<TouchableOpacity
onPress={props.onPress}
style={[
styles.block,
{
height: '8%',
width: '16.6%',
backgroundColor: props.isSelected ? '#F6CE0E' : 'white',
},
,
]}
/>
);
};
and
type PropsType = {
isSelected: boolean;
onPress: () => void;
};
const BlockItem: React.FC<PropsType> = (props) => {
return (
<TouchableOpacity
onPress={props.onPress}
style={[
styles.block,
{
height: '8%',
width: '16.6%',
backgroundColor: props.isSelected ? '#F6CE0E' : 'white',
},
,
]}
/>
);
};
I hope for best and i will appreciate help a lot, since i trying to solve this thingy for a quite some time now and its not getting any easier)

Creating a FlatList Grid from props data

Creating a screen that contains a FlatList grid of photos that I fetch from my rails API. Having trouble displaying the data - https://ibb.co/XkfSvxm. I wish for the images, regardless of how many there are, to be in a square format.
Currently, I can fetch and display the data in square photos except whenever there is 1 or 2 photos on a row. They fill the row rather than take the formatting from the formatItems method (which essentially pushes two empty items into the array forcing the data into a grid).
I've tried a bunch of things including returning the data straight from formatItems and plugging the method straight into the data of the flatlist, but then no data is loading. I also had it working well with a local SQL DB package.
How would I go around arranging so that I can format the data from props and display it correctly in the FlatList?
Here is the code:
class VaultScreen extends React.Component {
state = {
searchQuery: '',
refreshing: false,
setRefreshing: false
};
constructor(props) {
super(props);
this.state = {
items: {},
itemSelected: {}
};
this.props.fetchPosts()
}
componentDidMount() {
let itemData = this.props.posts
this.setState({
items: itemData,
});
// this.formatItems(itemData)
}
formatItems = () => {
const { items } = this.state
const newItems = []
const numberOfFullRows = Math.floor(items.length / 3);
// const numberOfFullRows = Math.floor(itemData.length / 3);
let numberOfElementsLastRow = 1
// items.length - (numberOfFullRows * 3);
while (numberOfElementsLastRow !== 3 && numberOfElementsLastRow !== 0) {
newItems.push({ key: `blank-${numberOfElementsLastRow}`, empty: true });
numberOfElementsLastRow++;
}
return this.setState({ items: newItems })
};
renderItem = ({ item, type }) => {
const { items } = this.state;
if (item.empty === true) {
return <View style={[styles.item, styles.itemInvisible]} />;
} else {
return (
<TouchableOpacity style={styles.item} onPressIn={() => this.setState({ itemSelected: item.id })} onPress={this.viewPhoto} key={item.id}>
<Image source={{ uri: item.image }} style={{ flex: 1, width: '100%', height: undefined }} />
</TouchableOpacity>
);
}
};
render() {
return (
<SafeAreaView style={{ flex: 1, backgroundColor: 'white' }}>
<FlatList
data={this.state.items}
renderItem={this.renderItem}
numColumns={3}
keyExtractor={(item, index) => index.toString()}
refreshControl={<RefreshControl refreshing={this.state.refreshing} onRefresh={() => this.onRefresh()} />}
/>
</SafeAreaView>
);
}
}
const mapStateToProps = state => ({
posts: state.vault.posts
})
const mapDispatchToProps = {
fetchPosts: () => fetchPosts(),
}
export default connect(mapStateToProps, mapDispatchToProps)(VaultScreen)
hi you can add minWidth and maxWidth styling to the parant View of the render method (in your case give style to TouchableOpacity ) value should be total_width / 3 (+-margin if you want to give) . and then add width:total_width to columnWrapperStyle of FlatList.
NOTE:
-- total_width = 100%
-- keeping minWidth and maxWidth values same will give you perfect view as per your expectation (In your case if total_width : 100% then minWidth and maxWidth values will be 30%)

React-Native : useState to update object inside array

I have a list of items, when one is clicked, it navigates to a modal which displays a list of options.
I am trying to increment the counter inside each option, it works as intended BUT when I exit the modal screen and go back to it, the options counter are not reseted.
const myOptions = [
{ id: '001', name: 'option 001', counter: 0 },
{ id: '002', name: 'option 002', counter: 0 },
];
function ModalScreen({ route, navigation }) {
const [options, setOptions] = useState(myOptions);
let tempArr = [...myOptions];
// Array where I increment the counter, before passing it to setOptions(tempArr)
useEffect(() => {
return () => {
// because of let tempArr = [...myOptions]; changes in tempArr are copied
in myOptions. I want to reset myOptions when I exit the component
console.log(options);
console.log(myOptions) // both output are identical
};
}, []);
return (
<View>
<Text style={{ fontWeight: 'bold', marginBottom: 15 }}>
Click on an option to increment counter by 1
</Text>
<FlatList
keyExtractor={item => item.name}
extraData={tempArr}
data={options}
renderItem={({ item, index }) => (
<TouchableOpacity
onPress={() => {
tempArr[index].counter++;
setOptions(tempArr);
}}>
<Text>
{item.name} - counter: {item.counter}
</Text>
</TouchableOpacity>
)}
/>
</View>
);
}
I did a demo here :
https://snack.expo.io/#oliviermtl/carefree-marshmallows
I spent my day trying to figure out this one... Let me know if something needs more explanation
Thanks
Change
useEffect(() => {
return () => {
console.log(options);
console.log(myOptions)
};
}, []);
to
useEffect(() => {
return () => {
myOptions[0].quantity = 0;
myOptions[1].quantity = 0;
};
},[]);
What I was doing is that changing quantity values to 0, whenever user closes or comes back out from modal.
Hope this helps!