I'm implementing Infinite Scroll with Cloud Firestore and using the lastVisible as the document reference to know where to being the refreshing queries for another 5 users (limit is set to 5).
I have the keyExtractor using the item.id, but still returning the error Warning: Each child in an array or iterator should have a unique "key" prop.%s%s even though I'm using item.id for the keyExtractor.
keyExtractor={(item) => {
item.id;
}}
I've also tried using item.id.toString() from a post on stackoverflow, but that didn't work either.
// Imports: Dependencies
import React, { Component } from "react";
import { ActivityIndicator, Dimensions, FlatList, View, SafeAreaView, ScrollView, StyleSheet, Text, TouchableOpacity } from 'react-native';
import * as firebase from 'firebase';
import 'firebase/firestore';
import firebaseConfig from '../config/config';
// Imports: Components
import UserSelector from '../components/UserSelector';
import TitleLarge from '../components/TitleLarge';
// Screen Dimensions
const { height, width } = Dimensions.get('window');
// Screen: Flat List (Users)
class FlatListUsers extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
limit: 5,
lastVisible: null,
loading: false,
refreshing: false,
};
}
// Component Will Mount
componentWillMount = () => {
// Firebase: Initialize
firebase.initializeApp({
apiKey: `${firebaseConfig.apiKey}`,
authDomain: `${firebaseConfig.authDomain}`,
databaseURL: `${firebaseConfig.databaseURL}`,
projectId: `${firebaseConfig.projectId}`,
storageBucket: `${firebaseConfig.storageBucket}`,
messagingSenderId: `${firebaseConfig.messagingSenderId}`,
});
}
// Component Did Mount
componentDidMount = () => {
this.retrieveUsers();
}
// Retrieve Users
retrieveUsers = async () => {
try {
// Set State: Loading
this.setState({ loading: true });
// Firebase: Database + Settings
const db = firebase.firestore();
// Query
console.log('Fetching Users')
const initialQuery = await db.collection('users')
.where('company', '==', 'Google')
.orderBy('first_name')
.limit(this.state.limit);
// Query Snapshot
const querySnapshot = await initialQuery.get();
// Document Data
console.log('Document Data');
const documentData = querySnapshot.docs.map(document => document.data());
console.log(documentData);
// Last Visible Document
let lastVisible = documentData[documentData.length - 1];
// Set State
await this.setState({
data: documentData,
lastVisible: lastVisible,
loading: false,
refreshing: false,
});
console.log('Last Visible');
console.log(typeof this.state.lastVisible)
console.log(JSON.stringify(this.state.lastVisible))
}
catch (error) {
console.log(error);
}
};
// Retrieve More Users
retrieveMore = async () => {
try {
const { lastVisible } = this.state;
console.log('Retrieving more Users (Retrieve More)');
this.setState({ loading: true });
await console.log(`Last Visible (Retrieve More): ${this.state.lastVisible}`);
// Firebase: Database + Settings
const db = firebase.firestore();
// Query
console.log('Paginated Query starting (Retrieve More)');
const paginatedQuery = await db.collection('licenses')
.where('company', '==', 'Google')
.orderBy('first_name')
.startAfter(lastVisible)
.limit(this.state.limit)
// Query Snapshot
const querySnapshot = await paginatedQuery.get();
console.log(`Query Snapshot (Retrieve More): ${querySnapshot}`);
// Document Data
const documentData = querySnapshot.docs.map(document => document.data());
console.log(`Document Data (Retrieve More): ${documentData}`);
// Last Visible Document (Used As Query Position)
let lastVisible = documentData[documentData.length - 1];
console.log(`Last Visible New (Retrieve More): ${lastVisible}`);
// Set State
this.setState({
data: [...this.state.data, ...documentData],
lastVisible: lastVisible,
loading: false,
refreshing: false,
});
}
catch (error) {
console.log(error);
}
};
// Render Header
renderHeader = () => {
try {
return (
<TitleLarge title="Users" />
)
}
catch (error) {
console.log(error);
}
};
// Render Footer
renderFooter = () => {
try {
// Check If Loading
if (this.state.loading) {
return (
<View style={styles.activityIndicator}>
<ActivityIndicator />
</View>
)
}
else {
return null;
}
}
catch (error) {
console.log(error);
}
};
render() {
return (
<SafeAreaView style={styles.container}>
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<UserSelector
key={(item, index) => {
return item.id;
}}
firstName={item.first_name}
lastName={item.last_name}
company={item.company}
/>
)}
keyExtractor={(item) => {
item.id;
}}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
onEndReached={()=>{
if (this.state.loading === false) {
this.retrieveMore();
}
}}
onEndReachedThreshold={0}
/>
</SafeAreaView>
)
}
}
// Styles
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
height: height,
width: width,
borderTopWidth: 0,
borderBottomWidth: 0,
},
scrollView:{
height: 'auto',
},
UserContainer: {
width: width,
marginBottom: 7,
},
itemText: {
fontFamily: 'System',
fontSize: 17,
fontWeight: '400',
color: '#222222',
marginLeft: 16,
},
activityIndicator: {
paddingVertical: 20,
borderTopWidth: 0,
borderTopColor: '#CED0CE',
},
});
// Exports
export default FlatListUsers
You just add keyExtractor into your FlatList code:
keyExtractor={(item, index) => String(index)}
Update below code and it will help you.
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<UserSelector
key={(item, index) => {
return item.id;
}}
firstName={item.first_name}
lastName={item.last_name}
company={item.company}
/>
)}
keyExtractor={(item) => {
item.id;
}}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
keyExtractor={(item, index) => String(index)}
onEndReached={() => {
if (this.state.loading === false) {
this.retrieveMore();
}
}}
onEndReachedThreshold={0}
/>
Regarding keyExtractor description:
keyExtractor tells the list to use the ids for the react keys instead of the default key property.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With