问题
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: '',
},
...
};
回答1:
The problem here lies within the fact that
- You are mutating an existing slice of state instead of creating a mutated copy
_onCategoryChosen = category => {
var oldReportCopy = this.state.report; // This does not create a copy!
oldReportCopy.selectedCategory = category;
this.setState(Object.assign({}, this.state, { report: oldReportCopy }));
};
This should be
_onCategoryChosen = category => {
var oldReportCopy = Object.assign({}, this.state.report);
oldReportCopy.selectedCategory = category;
// setState handles partial updates just fine, no need to create a copy
this.setState({ report: oldReportCopy });
};
The props of FlatList remain the same, your
_renderRowfunction may rely on theselectedCategoryprop which does change (If not for the first mistake), but the FlatList component does not know about that. To solve this, use the extraData prop.<FlatList data={this.props.items.categories.edges} renderItem={this._renderRow} horizontal={true} keyExtractor={(item, index) => item.node.id} extraData={this.props.selectedCategory} />
回答2:
Simply you can solve this problem passing props to extraData in flat list component like this,
<FlatList
data={this.props.data}
extraData={this.props}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
/>
回答3:
I agree with Nimelrian. Also, If your state is an Array you could create an Array Object from the state by doing:
var oldReportCopy = Object.assign([], this.state.report);
Then use the .push() method to add your new object to it like this:
oldReportCopy.push(selectedCategory);
you can then set this new Array Object back to state:
this.setState({ report: oldReportCopy });
回答4:
Maybe this won't be the case for anyone else but I realized I was only having trouble when the array of items being rendered by the FlatList became empty. In my case I just needed to not render the FlatList at all, and instead render a different View in its place, and that of course fixed my issue with it "not re-rendering".
来源:https://stackoverflow.com/questions/44278526/react-native-flatlist-not-rerendering-row-when-props-change