Why React Native FlatList style not change with state - react-native

I have some tabs. I want to change the color of the selected tab. I created a state for selected tab index which will hold the tab ID. When Tab is pressed the selected state change to the pressed tab ID. I am comparing the selected state to tab ID. If both are equal then the selected tab will have some different style.
But when state changes and condition is true, the selected tab is not changing its state. Why change in state do not trigger the comparison in the style to update the style?
<FlatList
horizontal
data={this.state.drinkgroup}
showsHorizontalScrollIndicator={false}
renderItem={({item, index}) =>
{
return <Tab
item={item}
selected={this.state.selected}
changeSelected={
() => {
this.setState({selected: item.id}, function(){ console.log(this.state.selected, item.id)
console.log(this.state.selected==item.id)
})
}}
}
}
/>
export const Tab = ({item, selected, changeSelected}) => {
return (
<TouchableOpacity
style={[styles.tabStyle, (selected==item.id)? styles.tabSelectedStyle: null]}
onPress={changeSelected}
underlayColor='#fff'
>
<Text style={{color: '#f2f2f2', textAlign: 'center', fontSize: 15}}>{item.name}</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
tabStyle: {
backgroundColor: '#800080',
height: 32,
paddingRight: 15,
paddingLeft: 15
},
tabSelectedStyle: {
borderBottomColor: 'white',
borderBottomWidth: 3
}
})

By passing extraData={this.state} to FlatList we make sure FlatList itself will re-render when the state.selected changes. Without setting this prop, FlatList would not know it needs to re-render any items because it is also a PureComponent and the prop comparison will not show any changes.
<FlatList
data={this.props.data}
extraData={this.state}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
/>
This is a PureComponent which means that it will not re-render if props remain shallow- equal.
Make sure that everything your renderItem function depends on is passed as a prop (e.g. extraData) that is not === after updates, otherwise your UI may not update on changes.
This includes the data prop and parent component state.
More Details :- FlatList

You need to provide extraData prop to the Flatlist, if you want it to re render its items. You can do it like extraData={this.state}.
By passing extraData={this.state} to FlatList we make sure FlatList itself will re-render when the state.selected changes. Without setting this extraData prop, FlatList would not know it needs to re-render any items because it is also a PureComponent and the prop comparison will not show any changes.
For further details you can visit official documentation here

Related

FlatList only updates after user interaction occurs

I have a component that gets data from navigation props which I use to set the state in CDM():
this.setState({
data: this.props.navigation.getParam('data', 'Some Data')
})
The state is then used in a FlatList. When props change.. the FlatList doesn't update... Until the client touches the screen.
I have tried putting the props directly into the FlatList:
data={this.props.navigation.getParam('data', 'Some Data')}
I have tried using the extraData prop:
<FlatList
style={{ flexGrow: 1, marginTop: 10 }}
data={this.state.data}
keyExtractor={(item, index) => index.toString()}
extraData={this.props.navigation.getParam('data', 'Some Data')}
renderItem={({ item }) => {...}>
I have tried using componentDidUpdate() to reset state when the props change but CDU never gets called.
EDIT:
I have a feeling it is because when I add items; I'm navigating back (navigation.goBack())
and goBack() dosnt refresh the state? i'm not sure.
Found it:
https://github.com/react-navigation/react-navigation/issues/922#issuecomment-344752635
It's essentially passing a function as a parameter and calling that function to change the state of the parent then calling goBack()

React Native Flatlist dynamic style

I'm trying to make buttons out of react native FlatList items, that means when I click on them they change color.
This is the function that gets rendered by the renderItem prop:
renderRows(item, index) {
return (
<TouchableOpacity
key={index}
style={[styles.item, this.calculatedSize()]}
onPress={() => this.onPressImage(index)}
>
<Image
style={[styles.picture]}
source={{ uri: item.uri }}
/>
<View
style={[
styles.imageTextContainer,
{
backgroundColor:
_.indexOf(this.active, index) === -1
? 'rgba(0,0,0,0.2)'
: 'rgba(26, 211, 132, 0.7)',
},
]}
>
<Text style={styles.imageText}>{item.title}</Text>
</View>
</TouchableOpacity>
)
}
oh yeah and im using loadash to get the index.
The function "onPressImage(index)" works fine, in "this.active" (array) I always have the positions(integer) of the elements I would like to change color,
however nothing happens, the only thing you can see is the response by the touchableOpacity. What am I doing wrong ?
Like Andrew said, you need to trigger a re-render, usually by updating state.
However, if the items don't depend on anything outside the FlatList, I would recommend creating a component (preferably a PureComponent if possible) for the items themselves, and updating their state upon press. This way it will only re-render each list item individually if there is a change instead of the parent component.
Thanks, updating the state was a little bit difficult, the items array I've used has been declared outside the class component, thus only using an "active" array with indices that should change color in the state wasn't enough, I had to map the items array in the state like so:
state = {
items: items.map(e => ({
...e,
active: false,
}))
}
then I could manipulate the active state of the element I wanted to change color like this:
onPressItem(index) {
const { items } = this.state
const newItems = [...this.state.items]
if (items[index].active) {
newItems[index].active = false
} else {
newItems[index].active = true
}
this.setState({ items: newItems })
}
and change the color like so:
style={{
backgroundColor: item.active
? 'rgba(26, 211, 132, 0.7)'
: 'rgba(0,0,0,0.2)',
}}

React Native FlatList onViewableItemsChanged Returning Incorrect set of Items

I am trying to use onViewableItemsChanged event to detect the items of my FlatList that are currently displayed on screen.
In my ViewabilityConfig (Code is provided below), I set the itemVisiblePercentThreshold parameter to 100 which I assumed will require my item to be fully displayed to be considered viewable. However that is not the case for me.
As you can see in the following screenshot:
Screenshot of my App
It is obvious that the top most item is not completely on screen (Which should make the visible items consist only of 3 items). But when I print the length of the array in my onViewableItemsChanged event handler, it returns 4 (and when I inspect the values, including the top most item).
Log Result of Viewable Items Array Length
Is this the problem of FlatList onViewableItemsChanged event? Or did I implemented it incorrectly?
I tried to find solution from the documentation and React-native github but there is no further explanation about how this event works.
Some related snippets of my code are as follow:
FlatList Definition
<FlatList
viewabilityConfig={this.clippingListViewabilityConfig}
inverted={true}
horizontal={false}
data = {this.props.clippingResultArray}
ref={(ref) => this.clippingResultFlatList = ref}
style={{
// flexGrow:0,
// backgroundColor: 'green',
// width:'100%',
// width: Dimensions.get('window').width,
}}
contentContainerStyle={{
// justifyContent:'flex-end',
// flexGrow:0,
// flexDirection:'row',
// alignItems:'flex-end',
}}
renderItem={this.renderClippingListItemRight}
keyExtractor={(item, index) => index.toString()}
onViewableItemsChanged={this.onClippingListViewableChanged}
// removeClippedSubviews={true}
{...this._clippingListItemPanResponder.panHandlers}
/>
onViewableItemsChanged Listener
onClippingListViewableChanged = (info) => {
console.log("***************************NUMBER OF CURRENT VIEWABLE ITEMS:",info.viewableItems.length);
console.log("Item list:",info.viewableItems);
this.setState({
...this.state,
viewableItems: info.viewableItems,
});
};
Viewable Configuration
this.clippingListViewabilityConfig = {
waitForInteraction: false,
itemVisiblePercentThreshold: 100,
minimumViewTime: 500, //In milliseconds
};

React native setState() not re-render as expected

I made a carousel with react-native-snap-carousel and below are the code for carousel. In carousel there're picker and textInput and etc.
For render function:
<Carousel
ref={(c) => { this._carousel = c; }}
data={this.state.question}
renderItem={this._renderItem}
sliderWidth={standard * 1.1}
itemWidth={standard * 0.9}
inactiveSlideScale={0.9}
inactiveSlideOpacity={0.7}
firstItem={0}
activeSlideAlignment={'center'}
containerCustomStyle={styles.slider}
contentContainerCustomStyle={styles.sliderContentContainer}
/>
For detail rendering:
_renderItem ({item, index}) {
var content;
switch (item.id) {
case 1:
content = <View style={styles.carousel_cell}>
<Text style={styles.carouselItemTitle}>{item.displayLabel}</Text>
<TextInput style={styles.editor} onChangeText={(text) => this.setState({comment: text})} value={this.state.comment} />
</View>;
break;
case 2:
content = <View style={styles.carousel_cell}>
<Text style={styles.carouselItemTitle}>{item.displayLabel}</Text>
<Picker mode="dropdown"
style={styles.picker}
selectedValue={this.state.priority}
onValueChange={(itemValue) => this.onPriorityChange(itemValue)}>
{this.state.list}
</Picker>
</View>;
break;
default:
content = <View style={styles.carousel_cell}>
<Text style={styles.carouselItemTitle}>{item.displayLabel}</Text>
<Text>Unknown</Text>
</View>;
break;
}
return content;
}
For event triggering:
onPriorityChange(value) {
this.setState({priority: value});
}
The problem here is that after I select any item in Picker the state did get updated but on the interface it's not. The render function of Picker will be invoked once I trigger the onChangeText event. At that time the display in Picker will be update as well.
However, according to the life cycle, shouldn't the carousel(including its cells) be updated/re-rendered when I setState?
The Stopwatch(from react-native-stopwatch-timer) in carousel will have the same problem.
I don't know this package but the Carousel component seems to inherit from FlatList component.
The documentation says:
By passing extraData={this.state} to FlatList we make sure FlatList itself will re-render when the state.selected changes. Without setting this prop, FlatList would not know it needs to re-render any items because it is also a PureComponent and the prop comparison will not show any changes.
So, try to add extraData={this.state} to your Carousel component.

Flatlist + React Native Router Flux: slow navigate transition

I have a datasource with more than 800 entries which I'm using FlatList to render it.
Each renderItem receives a function to navigate to another screen on item press.
The problem is that the transition between screens is extremely slow.
I noticed that even with scrolling working fast, renderItem is still being called for all 800 entries in DOM. When all items are finally rendered, then the navigation works fine.
I've tried using initialNumToRender, getItemLayout and waitForInteraction props, as well tried to change my renderItem component (now is a stateless component) to a pure component. Nothing seems to work so far.
Any suggestion will be appreciated.
Here's some code if may help:
<FlatList
data={this.state.listDataSource}
renderItem={({ item, index }) => this.renderListItem(item, index)}
keyExtractor={this._keyExtractor}
style={{
flex: 1,
marginHorizontal: 30,
borderTopWidth: 1,
borderColor: '#919191',
}}/>
renderListItem(item, index) {
return <ListItem dotFunc={() => this.onListItemPress(index)} item={item} />;
}
onListItemPress(index) {
Actions.itemDetail({
index
});
}
// ListItem.js correctly exported
const ListItem = ({ dotFunc, item }) => (
<TouchableOpacity onPress={() => Actions.contactDetail({rowID})}>
<Text>{Item}</Text>
</TouchableOpacity>
}
Thanks