react-native: Flatlist how to pass from onViewableItemsChanged to renderItem - react-native

I am trying to manipulate a component under renderItem in a FlatList when onViewableItemsChanged is called.
My code looks like this:
<FlatList
data={this.props.data.allPosts.nodes}
ListHeaderComponent={() => this.props.listHeader}
onViewableItemsChange={this.onViewableItemsChanged}
renderItem={({item}) =>
<View style={{ marginBottom: 12 }}>
<Video lights={true}
ref={(ref) => this[`postRef_${item.key}`] = ref}
/>
</View>
}
/>
onViewableItemsChanged = ({viewableItems}) => {
viewableItems.forEach((item) => {
const { isViewable, key } = item;
if(isViewable) {
const ref = this[`swiperRef_${key}`];
if(!ref) return console.log('Ref not found');
console.log('ref', ref)
ref.paused = false
}
});
}
My issue is that the reference keeps returning undefined. Any way around it?

I am not sure what you mean by "the reference" but if you are referring to const ref = this[swiperRef_${key}];
Have you bound this to onViewableItemsChanged via
this.onViewableItemsChanged = this.onViewableItemsChanged.bind(this)
or
onViewableItemsChange={this.onViewableItemsChanged.bind(this} ?

Related

Change state of RenderItem on screenLeave

Does anyone know how I can change the state of a renderItem when it leaves screen? Below I have the Flatlist with uses an Item, I want to change the state of the item once it exits the renderview.
const Item = memo(({content}) => {
const [outOfView, setOutOfView] = useState(false)
const onScroll= () => {
if (!outOfView) setOutOfView(true) //Trying to get this to work
}
return (
<View style={styles.item} onScroll={onScroll}>
<Text>{content.title}</Text>
</View>
)
})
const Slider = props => {
const flatList = useRef()
const _renderItem = ({ item, index }) => <Item content={item} />
return (
<View style={styles.container} >
{props.header ? <AppText style={styles.header} text={props.header} /> : null}
<FlatList
data={props.data}
horizontal
pagingEnabled
renderItem={_renderItem}
keyExtractor={item => item._id}
ref={flatList}
/>
</View>
)
}
YOu can do something like this
import { useIsFocused } from '#react-navigation/native';
const Item = memo(({content}) => {
const [outOfView, setOutOfView] = useState(false)
const onScroll= () => {
if (!outOfView) setOutOfView(true) //Trying to get this to work
}
const isFocused = useIsFocused();
return (
<View style={styles.item} onScroll={onScroll}>
<Text>{isFocused?content.title:"Offline"}</Text>
</View>
)
})
hope it helps. feel free for doubts

Adding Search Bar into Flatlist using Hook, Undefined

i try to create flatlist inside modal with search bar functionality that can filter the result based on user input in the search bar.
For the flatlist everything show up accordingly,
problem i can't filter the data,
i try using .filter from the original full data (list) but result is always undefined is not a data.
Btw data is populate from local .json file to state using useEffect.
Here is the local country.json data :
[
"Japan",
"Korea",
"Thailand",
"Indonesia" ]
Here is part of the source code :
import dataCountry from '../../assets/json/country.json';
const NameScreen = ({ navigation }) => {
// hook
const [list, setList] = useState([]);
const [modalBirthplace, setModalBirthplace] = useState(false);
const [searchText, setSearchText] = useState('');
useEffect(() => {
setList({ dataCountry });
console.log('check List : ', list);
}, [])
const renderItem = ({ item }) => (
<Item title={item.title} />
);
const ListItem = ({ title }) => (
<View>
<TouchableOpacity onPress={() => console.log("ok")}>
<Text style={styles.cityList}>{title}</Text>
</TouchableOpacity>
</View>
);
const searchFilterFunction = searchText => {
setSearchText(searchText);
console.log(searchText)
const newData = list.filter((item) => { // error trigger from this list.filter undefined is not a function
const itemData = item.toUpperCase();
const textData = searchText.toUpperCase();
return itemData.indexOf(textData) > -1;
});
setList(newData);
};
return (
<View style={styles.container}>
<Modal
animationType="slide"
transparent={true}
visible={modalBirthplace}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<Text style={styles.modalText}>Choose your country location :</Text>
<TextInput
placeholder="Try japan maybe?"
onChangeText={searchText => searchFilterFunction(searchText)}
value={searchText}
/>
<FlatList
data={list.dataCountry}
renderItem={({ item }) => (
<ListItem
title={item}
/>
)}
keyExtractor={item => item}
/>
<TouchableHighlight
style={{ ...styles.openButton, backgroundColor: '#E15C72' }}
onPress={() => {
setModalBirthplace(!modalBirthplace);
}}>
<Text style={styles.textStyle}>Close Selection</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
</View>
)
}
Anybody know why i can't filter the state?
Thanks a lot before
the problem is your state is an JSON object, not an array:
setList({ dataCountry });
// so list is:
{
dataCountry: [
...
]
}
so, you need to change here
const newData = list.dataCountry.filter((item) => { // here
const itemData = item.toUpperCase();
const textData = searchText.toUpperCase();
return itemData.indexOf(textData) > -1;
});
setList({dataCountry: newData}); // and here
maybe your json like this,
const list = {
dataCountry : [
'UK',
'US'
]
}
List is an object you can't use the filter with an object.
Instead of using array placeholder you can use spread operator like this,
const newData = [...list.dataCountry].filter((item) => {
const itemData = item.toUpperCase();
const textData = searchText.toUpperCase();
return itemData.indexOf(textData) > -1;
});

React Native Hooks - Trying to refactor from class Component to Function Component to use hooks

I'm new in react native and I'm tryng to refactor this code below, and I think I'm doing some thing wrong here "setSentences(item)", because it's not updating.
Do you know what i'm doing wrong here?
this.state = {
sentences: [],
};
{this.state.sentences.map((item) => {
return(
<TouchableOpacity
onPress={() => {
item.selected = false;
this.setState(item);
}}>
</TouchableOpacity>
)}}
Refactored:
const [sentences, setSentences] = useState([]);
{sentences.map((item) => {
return (
<TouchableOpacity
onPress={() => {
item.selected = false;
setSentences(item);
}}>
</TouchableOpacity>
sentences is an array but when you show list array setSentences update array sentences. Something wrong here
const [sentences, setSentences] = useState([]);
{sentences.map((item) => {
return (
<TouchableOpacity
onPress={() => {
item.selected = false;
setSentences(item);
}}>
</TouchableOpacity>
I think you can use useRef to store array sentences and another state to set item
const sentences = useRef([]);
const [itemSentence, setItemSentence] = useState('');
<Text>{itemSentence}</Text>
{sentences.current.map((item) => {
return (
<TouchableOpacity
onPress={() => {
item.selected = false;
setItemSentence(item);
}}>
</TouchableOpacity>
For clear solution you have to create one method like this :
const onItemPress = (itemIndex) => {
const temp = [].concat(sentences);
temp[itemIndex].selected = false;
setSentences(temp);
}
Now, call this method on TouchableOpacity onPress method and pass current index to this method to modify that perticular item state as below :
{sentences.map((item, itemIndex) => {
return (
<TouchableOpacity
onPress={() => onItemPress(itemIndex)}>
</TouchableOpacity>
)
}}
And note, your state should be :
const [sentences, setSentences] = useState([]);
i think you need clone array sentences and get index inside
const [sentences, setSentences] = useState([]);
sentences.map((item:any,index:number) => {
return (
<TouchableOpacity
onPress={() => {
const cloneSentences =clone(sentences);
cloneSentences[index].selected = false;
setSentences(cloneSentences);
}}>
</TouchableOpacity>)
})

useEffect and setState on async backend calls not re-rendering component

We have a cardList react native component that is a child of search component.
export default function CardList(props) {
keyExtractor = (item, index) => index.toString()
renderItem = ({ item }) => (
<ListItem
title={item.name}
subtitle={item.subtitle}
leftAvatar={{
source: item.avatar_url && { uri: item.avatar_url },
title: item.name[0]
}}
bottomDivider
chevron
/>
)
return (
<FlatList
keyExtractor={keyExtractor}
data={props.images}
renderItem={renderItem}
/>
);
}
The Search fetches data async from backend which takes a couple of seconds and is done with useEffect, for some reason the setKeys in useEffect does not re-render the cardList component. When I refresh artificially with hot-reload on expo the cardList renders fine. Why does setKeys (useState) not render the component?
Thanks for any help!
const [keys, setKeys] = useState([]);
useEffect(() => {
const fetchData = async () => {
const imgkeys = await << 5 second long backend call >>;
setKeys(imgkeys);
}
fetchData();
}, []);
return (
<View>
<View style={{
padding: 5,
}}>
{ (keys) && (keys.length>0) && <CardList images={keys}/> }
</View>
</View>
);
setState is asynchronous for performance reasons and shouldn't be forced to be synchronous just because state updates weren't performed correctly.
You can simply define the useState like that:
const [, forceUpdate] = React.useState(0);
forceUpdate(n => !n)

FlatList ScrollView Error on any State Change - Invariant Violation: Changing onViewableItemsChanged on the fly is not supported

onViewableItemsChanged does not seem to work when there is a state change in the app. Is this correct?
Seems like it wouldn't be very useful if this were the case....
Otherwise, users will be forced to us onScroll in order to determine position or something similar...
Steps to Reproduce
Please refer to snack
Repo has also been uploaded at github
Any state change produces an error when using onViewableItemsChanged
What does this error even mean?
Note: Placing the onViewableItemsChanged function in a const outside the render method also does not assist...
<FlatList
data={this.state.cardData}
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
onViewableItemsChanged={(info) =>console.log(info)}
viewabilityConfig={{viewAreaCoveragePercentThreshold: 50}}
renderItem={({item}) =>
<View style={{width: width, borderColor: 'white', borderWidth: 20,}}>
<Text>Dogs and Cats</Text>
</View>
}
/>
Actual Behavior
Error
Based on #woodpav comment. Using functional components and Hooks.
Assign both viewabilityConfig to a ref and onViewableItemsChanged to a useCallback to ensure the identities are stable and use those. Something like below:
const onViewCallBack = React.useCallback((viewableItems)=> {
console.log(viewableItems)
// Use viewable items in state or as intended
}, []) // any dependencies that require the function to be "redeclared"
const viewConfigRef = React.useRef({ viewAreaCoveragePercentThreshold: 50 })
<FlatList
horizontal={true}
onViewableItemsChanged={onViewCallBack}
data={Object.keys(cards)}
keyExtractor={(_, index) => index.toString()}
viewabilityConfig={viewConfigRef.current}
renderItem={({ item, index }) => { ... }}
/>
The error "Changing onViewableItemsChanged on the fly is not supported" occurs because when you update the state, you are creating a new onViewableItemsChanged function reference, so you are changing it on the fly.
While the other answer may solve the issue with useRef, it is not the correct hook in this case. You should be using useCallback to return a memoized callback and useState to get the current state without needing to create a new reference to the function.
Here is an example that save all viewed items index on state:
const MyComp = () => {
const [cardData] = useState(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']);
const [viewedItems, setViewedItems] = useState([]);
const handleVieweableItemsChanged = useCallback(({ changed }) => {
setViewedItems(oldViewedItems => {
// We can have access to the current state without adding it
// to the useCallback dependencies
let newViewedItems = null;
changed.forEach(({ index, isViewable }) => {
if (index != null && isViewable && !oldViewedItems.includes(index)) {
if (newViewedItems == null) {
newViewedItems = [...oldViewedItems];
}
newViewedItems.push(index);
}
});
// If the items didn't change, we return the old items so
// an unnecessary re-render is avoided.
return newViewedItems == null ? oldViewedItems : newViewedItems;
});
// Since it has no dependencies, this function is created only once
}, []);
function renderItem({ index, item }) {
const viewed = '' + viewedItems.includes(index);
return (
<View>
<Text>Data: {item}, Viewed: {viewed}</Text>
</View>
);
}
return (
<FlatList
data={cardData}
onViewableItemsChanged={handleVieweableItemsChanged}
viewabilityConfig={this.viewabilityConfig}
renderItem={renderItem}
/>
);
}
You can see it working on Snack.
You must pass in a function to onViewableItemsChanged that is bound in the constructor of the component and you must set viewabilityConfig as a constant outside of the Flatlist.
Example:
class YourComponent extends Component {
constructor() {
super()
this.onViewableItemsChanged.bind(this)
}
onViewableItemsChanged({viewableItems, changed}) {
console.log('viewableItems', viewableItems)
console.log('changed', changed)
}
viewabilityConfig = {viewAreaCoveragePercentThreshold: 50}
render() {
return(
<FlatList
data={this.state.cardData}
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
onViewableItemsChanged={this.onViewableItemsChanged}
viewabilityConfig={this.viewabilityConfig}
renderItem={({item}) =>
<View style={{width: width, borderColor: 'white', borderWidth: 20,}}>
<Text>Dogs and Cats</Text>
</View>}
/>
)
}
}
In 2023 with react-native version 0.71.2, the following code seems to work better than the older answers.
// 1. Define a function outside the component:
const onViewableItemsChanged = (info) => {
console.log(info);
};
// 2. create a reference to the function (above)
const viewabilityConfigCallbackPairs = useRef([
{ onViewableItemsChanged },
]);
<FlatList
data={this.state.cardData}
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
viewabilityConfig={{viewAreaCoveragePercentThreshold: 50}}
// remove the following statement
// onViewableItemsChanged={(info) =>console.log(info)}
// 3. add the following statement, instead of the one above
viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs.current}
renderItem={({item}) =>
<View style={{width: width, borderColor: 'white', borderWidth: 20,}}>
<Text>Dogs and Cats</Text>
</View>
}
/>
Source: https://github.com/facebook/react-native/issues/30171#issuecomment-820833606
const handleItemChange = useCallback( ({viewableItems}) => {
console.log('here are the chaneges', viewableItems);
if(viewableItems.length>=1)
viewableItems[0].isViewable?
setChange(viewableItems[0].index):null;
},[])
try this one it work for me
Setting both onViewableItemsChanged and viewabilityConfig outside the flatlist solved my problem.
const onViewableItemsChanged = useCallback(({ viewableItems }) => {
if (viewableItems.length >= 1) {
if (viewableItems[0].isViewable) {
setItem(items[viewableItems[0].index]);
setActiveIndex(viewableItems[0].index);
}
}
}, []);
const viewabilityConfig = {
viewAreaCoveragePercentThreshold: 50,
};
I'm using functional component and my flatlist looks like this
<Animated.FlatList
data={items}
keyExtractor={item => item.key}
horizontal
initialScrollIndex={activeIndex}
pagingEnabled
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
ref={flatlistRef}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { x: scrollX } } }],
{ useNativeDriver: false },
)}
contentContainerStyle={{
paddingBottom: 10,
}}
showsHorizontalScrollIndicator={false}
renderItem={({ item }) => {
return (
<View style={{ width, alignItems: 'center' }}>
<SomeComponent item={item} />
</View>
);
}}
/>
Try using viewabilityConfigCallbackPairs instead of onViewableItemsChanged.
import React, {useRef} from 'react';
const App = () => {
// The name of the function must be onViewableItemsChanged.
const onViewableItemsChanged = ({viewableItems}) => {
console.log(viewableItems);
// Your code here.
};
const viewabilityConfigCallbackPairs = useRef([{onViewableItemsChanged}]);
return (
<View style={styles.root}>
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
viewabilityConfigCallbackPairs={
viewabilityConfigCallbackPairs.current
}
/>
</View>
);
}
Move the viewabilityConfig object to the constructor.
constructor() {
this.viewabilityConfig = {
viewAreaCoveragePercentThreshold: 50
};
}
render() {
return(
<FlatList
data={this.state.cardData}
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
onViewableItemsChanged={(info) =>console.log(info)}
viewabilityConfig={this.viewabilityConfig}
renderItem={({item}) =>
<View style={{width: width, borderColor: 'white', borderWidth: 20,}}>
<Text>Dogs and Cats</Text>
</View>
}
/>
)
}
Sombody suggest to use extraData property of Flatlist to let Flatlist notice, that something changed.
But this didn't work for me, here is what work for me:
Use key={this.state.orientation} while orientation e.g is "portrait" or "landscape"... it can be everything you want, but it had to change, if the orientation changed.
If Flatlist notice that the key-property is changed, it rerenders.
works for react-native 0.56
this works for me, is there any way to pass an additional argument to onViewRef? Like in the below code how can i pass type argument to onViewRef.
Code:
function getScrollItems(items, isPendingList, type) {
return (
<FlatList
data={items}
style={{width: wp("100%"), paddingLeft: wp("4%"), paddingRight: wp("10%")}}
horizontal={true}
keyExtractor={(item, index) => index.toString()}
showsHorizontalScrollIndicator={false}
renderItem={({item, index}) => renderScrollItem(item, index, isPendingList, type)}
viewabilityConfig={viewConfigRef.current}
onViewableItemsChanged={onViewRef.current}
/>
)
}
Remove your viewabilityConfig prop to a const value outside the render functions as well as your onViewableItemsChanged function