I'm having an issue with the new FlatList component. Specifically, it does not rerender it's rows, even though props that the row is dependent on changes.
The FlatList docs says that:
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 that is not === after updates, otherwise your UI may not update on changes. This includes the data prop and parent component state.
THE QUESTION
However, seeing as I change an ID of the selectedCategory item - the prop that should indicate whether the row is 'selected' or not - I believe that the props should rerender. Am I mistaken?
I checked the 'componentWillReceiveProps' methods of both the list and row components, and the list receives the update just fine, but the row's lifecycle method is never called.
If I include a random, useless boolean state value in the list component, and switch it back and forth when the props update, it works - but I don't know why?
state = { updated: false };
componentWillReceiveProps(nextProps) {
this.setState(oldstate => ({
updated: !oldstate.updated,
}));
}
<FlatList
data={this.props.items.allAnimalCategories.edges}
renderItem={this._renderRow}
horizontal={true}
keyExtractor={(item, index) => item.node.id}
randomUpdateProp={this.state.updated}
/>
THE CODE
The structure of my code is this: I have a container component with all the logic and state, which contains a FlatList component (presentational, no state), which again contains a custom presentational row.
Container
Custom list component that includes the FlatList component
(presentational, stateless) and the renderRow method
Custom row (presentational, stateless)
The container includes this component:
<CustomList
items={this.props.viewer}
onCategoryChosen={this._onCategoryChosen}
selectedCategory={this.state.report.selectedCategory}
/>
CustomList:
class CustomList extends Component {
_renderRow = ({ item }) => {
return (
<CustomListRow
item={item.node}
selectedCategory={this.props.selectedCategory}
onPressItem={this.props.onCategoryChosen}
/>
);
};
render() {
return (
<View style={_styles.container}>
<FlatList
data={this.props.items.categories.edges}
renderItem={this._renderRow}
horizontal={true}
keyExtractor={(item, index) => item.node.id}
randomUpdateProp={this.state.updated}
/>
</View>
);
}
}
(data comes from Relay)
Finally the row:
render() {
const idsMatch = this.props.selectedCategory.id == this.props.item.id;
return (
<TouchableHighlight onPress={this._onItemPressed}>
<View style={_styles.root}>
<View style={[
_styles.container,
{ backgroundColor: this._getBackgroundColor() },
]}>
{idsMatch &&
<Image
style={_styles.icon}
source={require('./../../res/img/asd.png')}
/>}
{!idsMatch &&
<Image
style={_styles.icon}
source={require('./../../res/img/dsa.png')}
/>}
<Text style={_styles.text}>
{capitalizeFirstLetter(this.props.item.name)}
</Text>
</View>
<View style={_styles.bottomView}>
<View style={_styles.greyLine} />
</View>
</View>
</TouchableHighlight>
);
}
The row is not that interesting, but I included it to show that it is entirely stateless and dependent on it's parents props.
The state is updated like so:
_onCategoryChosen = category => {
var oldReportCopy = this.state.report;
oldReportCopy.selectedCategory = category;
this.setState(Object.assign({}, this.state, { report: oldReportCopy }));
};
State looks like this:
state = {
...
report: defaultStateReport,
};
const defaultStateReport = {
selectedCategory: {
id: 'some-long-od',
name: '',
},
...
};