Open bantingGamer opened 4 years ago
Hi @bantingGamer,
To be honest, these components were never tested with hooks. They (still) work using a HoC approach and further more will try to hijack the ref of the inner component for their own use (i.e the HoC are not "clean").
We'v been thinking about implementing a hooks-based version of these but never had the time. If you like you could attempt to help there with a PR. @bevkoski or @kern3lpan1c might be also interested in helping out
Thank you for getting back to me, I really appreciate it.
I thought it wouldn't be the worst thing to refactor and use a Class Component.
Concerning your request for me to attempt to convert it to use hooks, is a little beyond my scope, Im a bit of a newbie when it comes to coding, I wouldn't even know where to begin :P.
Have a wonderful day kind regards Luke
Thank you too @bantingGamer, I will keep this open still.
@bantingGamer When you got it working by using a class component were you creating the ref variable with React.createRef()
?
I tried your solution but am still getting errors. 😞
Hey @brax10ward
to answer your question, i am not using createRef(). can you please provide sample code that isnt working, ideally a https://snack.expo.io/ would be awesome?
this should do the trick in a class component.
<View>
<Viewport.Tracker>
<FlatList
ref={(ref) => (this.flatList = ref)}
data={DATA}
renderItem={this.renderItem}
keyExtractor={(item) => item.id}
/>
</Viewport.Tracker>
<Button
title={'FlatList ref button'}
onPress={() => {
console.log('flatList ref value: ', this.flatList);
}}
/>
</View>
Thanks for the response @bantingGamer. I will see if I can provide a sample on snack. Can you tell me how your are creating this.flatlist
?
@brax10ward
I believe that this is the line where it is created.
ref={(ref) => (this.flatList = ref)}
The this keyword is very confusing, I dont fully understand it myself that is why I prefer using functional components. You might want to brush up on how it works
@bantingGamer Can you share your whole component?
@brax10ward sure here is the entire component snack example
you can just add
<Viewport.Tracker>
...
</Viewport.Tracker>
and it should work exactly the same
import React, { Component } from 'react';
import { View, FlatList, StyleSheet, Text, Button } from 'react-native';
const DATA = [
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abbs28ba',
title: 'item one',
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aass97f63',
title: 'item two',
},
{
id: '58694a0f-3da1-471f-bd96-145571esss29d72',
title: 'item three',
},
{
id: 'bd7acbea-c1b1-46c2-aed5-123546',
title: 'item four',
},
{
id: '3ac68afc-c605-48d3-a4f8-45678888',
title: 'item five',
},
{
id: '58694a0f-3da1-471f-bd96-1455767867867861e29d72',
title: 'item six',
},
{
id: 'bd7acbea-c1b1-46c2-aed5-fdds',
title: 'item six',
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa9kkukfuk7f63',
title: 'item seven',
},
{
id: '58694a0f-3da1-471f-bd96-23dfghh',
title: 'item eight',
},
{
id: 'bd7acbea-c1b1-46c2-aed5-6776899',
title: 'item nine',
},
{
id: '3ac68afc-c605-48d3-a4f8-111222',
title: 'item ten',
},
{
id: '58694a0f-3da1-471f-bd96-1234',
title: 'item eleven',
},
];
const Item = ({ title }) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const renderItem = ({ item }) => <Item title={item.title} />;
class App extends Component {
render() {
return (
<View
collapsable={false}
style={{
flex: 1,
alignItems: 'center',
width: '100%',
// backgroundColor: 'red'
}}>
<FlatList
style={{
height: 100,
}}
ref={(ref) => (this.flatList = ref)}
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
<Button
title={'press to scroll'}
onPress={() => {
this.flatList.scrollToIndex({ index: 0 }); // scroll a bit before pressing the button
// or
// this.flatList.scrollToOffset({ offset: 300 });
}}
/>
</View>
);
}
}
const styles = StyleSheet.create({
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 32,
},
});
export default App;
Any update on this? I am having the issue with react navigation 5, useScrollToTop(). It takes a ref of the flatlist, which is not working in a function component
@brax10ward sure here is the entire component snack example
you can just add
<Viewport.Tracker> ... </Viewport.Tracker>
and it should work exactly the same
import React, { Component } from 'react'; import { View, FlatList, StyleSheet, Text, Button } from 'react-native'; const DATA = [ { id: 'bd7acbea-c1b1-46c2-aed5-3ad53abbs28ba', title: 'item one', }, { id: '3ac68afc-c605-48d3-a4f8-fbd91aass97f63', title: 'item two', }, { id: '58694a0f-3da1-471f-bd96-145571esss29d72', title: 'item three', }, { id: 'bd7acbea-c1b1-46c2-aed5-123546', title: 'item four', }, { id: '3ac68afc-c605-48d3-a4f8-45678888', title: 'item five', }, { id: '58694a0f-3da1-471f-bd96-1455767867867861e29d72', title: 'item six', }, { id: 'bd7acbea-c1b1-46c2-aed5-fdds', title: 'item six', }, { id: '3ac68afc-c605-48d3-a4f8-fbd91aa9kkukfuk7f63', title: 'item seven', }, { id: '58694a0f-3da1-471f-bd96-23dfghh', title: 'item eight', }, { id: 'bd7acbea-c1b1-46c2-aed5-6776899', title: 'item nine', }, { id: '3ac68afc-c605-48d3-a4f8-111222', title: 'item ten', }, { id: '58694a0f-3da1-471f-bd96-1234', title: 'item eleven', }, ]; const Item = ({ title }) => ( <View style={styles.item}> <Text style={styles.title}>{title}</Text> </View> ); const renderItem = ({ item }) => <Item title={item.title} />; class App extends Component { render() { return ( <View collapsable={false} style={{ flex: 1, alignItems: 'center', width: '100%', // backgroundColor: 'red' }}> <FlatList style={{ height: 100, }} ref={(ref) => (this.flatList = ref)} data={DATA} renderItem={renderItem} keyExtractor={(item) => item.id} /> <Button title={'press to scroll'} onPress={() => { this.flatList.scrollToIndex({ index: 0 }); // scroll a bit before pressing the button // or // this.flatList.scrollToOffset({ offset: 300 }); }} /> </View> ); } } const styles = StyleSheet.create({ item: { backgroundColor: '#f9c2ff', padding: 20, marginVertical: 8, marginHorizontal: 16, }, title: { fontSize: 32, }, }); export default App;
I see that you are using it fine as a class component, but how can we make it work with a function component. When assigning a ref to the flatlist, it gets ignored inside the viewport wrapper.
Hi @bantingGamer,
To be honest, these components were never tested with hooks. They (still) work using a HoC approach and further more will try to hijack the ref of the inner component for their own use (i.e the HoC are not "clean").
We'v been thinking about implementing a hooks-based version of these but never had the time. If you like you could attempt to help there with a PR. @bevkoski or @kern3lpan1c might be also interested in helping out
@servonlewis this is currently an issue.
Hey guys,
for all you react hook users, I was able to use the native controls from within Flatlist to achieve a viewport checker.
if interested, below is the code i used to make it work.
I am now able to use the flatlist ref, and keep my react hooks
// flatlist, requires viewabilityConfig and onViewableItemsChanged
<FlatList
scrollEventThrottle={16}
{...{
onViewableItemsChanged,
keyExtractor,
data,
onScroll,
ref,
renderItem,
refreshing,
onRefresh,
viewabilityConfig,
}}
/>
const viewabilityConfig = {
waitForInteraction: true,
itemVisiblePercentThreshold: 30
};
//use some state to capture the id of the viewport you are capturing
const [videoPlaying, setVideoPlaying] = useState<number | null>(null);
//useCallback is import because this function cannot rerender or it'll error, as per docs.
//for me, i only wanted to capture the viewport of an item that has the video prop.
const onViewableItemsChanged = useCallback(
(obj: { viewableItems: ViewToken[]; changed: ViewToken[] }) => {
const isPlaying = obj.viewableItems?.find((x) => {
const item: Props = x?.item;
return !!item?.video;
});
isPlaying
? setVideoPlaying((isPlaying?.item as Props)?.id)
: setVideoPlaying(null);
},
[]
);
//pass that state to your component, then elsewhere, I created my video player component like this, where it only plays if the video prop, and the id matches.
interface Props {
id: number;
videoPlaying?: number | null;
}
export const VideoForCard = ({ id, videoPlaying }: Props) => {
const [shouldPlay, setShouldPlay] = useState(false);
const isFocused = useIsFocused();
useEffect(() => {
id === videoPlaying ? setShouldPlay(true) : setShouldPlay(false);
}, [id, videoPlaying]);
useEffect(() => {
if (!isFocused) setShouldPlay(false);
if (isFocused && id === videoPlaying) setShouldPlay(true);
}, [isFocused]);
return (
<StyledVideo
source={{
uri: "http://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4",
}}
rate={1.0}
volume={1.0}
resizeMode="cover"
isMuted={true}
useNativeControls
{...{ preTriggerRatio, shouldPlay }}
/>
);
};
I'm having the same issue with functional components.
I was able to get this working with hooks, by using the following syntax:
<ScrollView ref={ref => (scrollView.current = ref)}
instead of
<ScrollView ref={scrollView}>
🙌
This worked for me
const flatList = useRef();
<FlatList ref={flatList} />
<Button
title="next"
onPress={() => flatList.current.scrollToIndex({ index: 1 })}
/>
Cannot get ref for <FlatList /> wrapped inside a</Viewport.Tracker> using functional component
but when using a class component it works fine
I have tried React.forwardRef but that doesnt seem to work either. As soon as i remove it works
Please can you assist?
kind Regards Luke