Reputation: 2075
When I hit the end of the list, fetchmore and updateQuery run the query with the new offset and fetch the new items. But the list itself re-renders with the old data and offset.
I've added the code here for review. Summarizing it, there's a SectionList of recent transactions, which is basically a regular FlatList with items grouped by date here. To get the infinite scroll to work, I followed the docs here for pagination using offset. For some reason, the offset is not increasing to match the size of the list each time I try to fetch more - instead, it updates every other time. How can this be? Console.log statements seem to show the list re-rendering BEFORE the new data is returned.
Log from the first time I hit the end of my list:
EndReached 1: offset is: 10
Top of query function
Top of query function
Query got transactions, returning txnList. txns.length: 10
TransactionList Render: transactions.length: 10
EndReached - UpdateQuery fn... offset: 10
EndReached - UpdateQuery fn... Prev length: 10
EndReached - UpdateQuery fn... Fetchmore length: 10
EndReached - UpdateQuery fn... New data length: 20 . <---this gets returned and should cause a re-render but it doesn't
Log when I scroll to the end of the list a second time:
EndReached: offset is: 10 <-- should be 20
Top of query function
Top of query function
Query got transactions, returning txnList. txns.length: 20 <--seems to be the transactions from the first updateQuery, this should be 10 new ones
TransactionList Render: transactions.length: 20
EndReached - UpdateQuery fn... offset: 10
EndReached - UpdateQuery fn... Prev length: 20
EndReached - UpdateQuery fn... Fetchmore length: 10
EndReached - UpdateQuery fn... New data length: 20 <--merged, skipping the duplicates 11-20 because we already had them from before
code
export class TransactionQuery extends React.Component {
constructor(props) {
super(props);
}
renderEndReached = (fetchMore, offset) => {
fetchMore({
variables: {
limit: this.props.limit,
offset: offset,
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) {
return prev;
}
copy = prev;
copy.user.transactions = unionBy(
prev.user.transactions,
fetchMoreResult.user.transactions,
"id"
); // this merges old transactions with new, removing any duplicates by matching transaction ID
return copy;
}
});
}
};
render() {
return (
<Query
query={TXN_DATA}
fetchPolicy="cache-and-network"
notifyOnNetworkStatusChange
variables={{ limit: this.props.limit, offset: 0 }}
>
{({ data, error, loading, fetchMore }) => {
if (error) return <Text>ERROR! O NO! {error}</Text>;
if (loading) {
return <Text> LOADING </Text>;
} // this is annoying and causes the list to snap back to the top each time we fetch more items.
if (data && data.user) {
const transactions = data.user.transactions;
if (transactions) {
return (
<TransactionList
transactions={transactions}
renderEndReached={() => this.renderEndReached(fetchMore=fetchMore, offset=transactions.length)}
/>
);
} else {
return <Text>No transactions found</Text>;
}
} else {
return <Text>Transactions query returned no data/user</Text>;
}
}}
</Query>
);
}
}
const Transaction = ({ name, date, amount, logoUrl }) => (
<ListItem icon noBorder>
<Left>
<Thumbnail small source={{ uri: logoUrl }} />
</Left>
<Body>
<Text>{name}</Text>
</Body>
<Right>
<USD value={amount} />
</Right>
</ListItem>
);
class TransactionList extends React.Component {
constructor(props) {
super(props);
}
renderItem = ({ item, index, section }) => {
return <Transaction {...item} />;
};
renderHeader = ({ section }) => {
return (
<ListItem itemDivider style={{ backgroundColor: "white" }}>
<Moment element={Text} format="dddd, MMMM D">
{section.title}
</Moment>
</ListItem>
);
};
render() {
const byDate = groupBy(this.props.transactions, "date");
const dates = Object.keys(byDate)
.sort()
.reverse();
return (
<SectionList
sections={dates.map(date => ({ title: date, data: byDate[date] }))}
renderItem={this.renderItem}
renderSectionHeader={this.renderHeader}
keyExtractor={item => item.id}
onEndReached={this.props.renderEndReached}
/>
);
}
}
Upvotes: 0
Views: 769
Reputation: 2075
Fix:
It seems Query won't re-render if it doesn't think the merged object (copy) is different enough from the previous query result - maybe it's a shallow compare? I discovered this fix by adding a test message to the object before returning it.
copy = prev;
copy.user.transactions = [...prev.user.transactions, ...fetchMoreResult.user.transactions]; // this change isn't detected. Shallow compare?
copy.message = "Hi there"; // this fixes the issue but is ugly
return copy;
A neater fix is to use lodash cloneDeep
copy = cloneDeep(prev);
copy.user.transactions = [...prev.user.transactions, ...fetchMoreResult.user.transactions];
return copy;
Upvotes: 1