Je fais un projet natif réactif où l'utilisateur peut rechercher des images à l'aide de l'API Flickr, tout le reste fonctionne bien, mais le problème que j'ai lors de la mise en œuvre de la pagination. J'ai utilisé onEndReached
de FlatList pour détecter quand l'utilisateur a défilé jusqu'à la fin de la liste, mais le problème est que onEndReached
est appelé plusieurs fois (dont un lors du premier rendu). J'ai même désactivé le rebond comme dit ici mais il est toujours appelé plus d'une fois
export default class BrowserHome extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
tagParam: "cat",
pageNum: -1,
data: [],
photosObj: ""
};
}
componentDidMount() {
this.setState({
isLoading: true
});
try {
this.makeRequest();
} catch {
console.log("error has occurred");
}
}
makeRequest = () => {
const { tagParam, pageNum } = this.state;
let url = `https://api.flickr.com/services/rest/?
method=flickr.photos.search
&api_key=${apiKey}&format=json&tags=${tagParam}
&per_page=30&page=${pageNum + 1}&nojsoncallback=1`;
fetch(url, {
method: "GET"
})
.then(response => response.json())
.then(responseJSON => {
this.setState({
data: this.state.data.concat(responseJSON.photos.photo),
isLoading: false,
pageNum: responseJSON.photos.page
});
})
.catch(error => {
console.log(error);
this.setState({ isLoading: false });
throw error;
});
};
render() {
if (this.state.isLoading) {
return <ActivityIndicator animating={true} size="large" />;
}
return (
<View
style={{
flex: 1,
height: 200,
justifyContent: "flex-start",
width: screenSize.width,
backgroundColor: "black"
}}
>
<Text>This is browserhome</Text>
<FlatList
style={{
width: screenSize.width
}}
numColumns={3}
data={this.state.data}
keyExtractor={item => item.id}
bounces={false}
onEndReachedThreshold={1}
onEndReached={({ distanceFromEnd }) => {
this.loadMoreItem();
alert("end reached call");
}}
renderItem={({ item, index }) => (
<>
<ImageTile imageURL={this.createImageURL(item)} />
// <Text style={{ color: "white" }}>
// {index}
// {console.log(index)}
// </Text>
</>
)}
/>
</View>
);
}
createImageURL(item) {
let server = item.server,
id = item.id,
secret = item.secret;
let urlString = `https://farm${
item.farm
}.staticflickr.com/${server}/${id}_${secret}_s.jpg`;
return urlString;
}
loadMoreItem() {
this.makeRequest();
}
}
Voici comment j'ai résolu mon problème:
Voici mon état initial:
state = {
onEndReachedCalledDuringMomentum: true,
lastLoadCount: 0,
}
Ceci est ma FlatList
<FlatList
keyboardShouldPersistTaps="always"
style={...}
data={this.state.searchResults}
extraData={this.state}
bounces={false}
renderItem={({ item, index }) =>
<SearchResultView
uriSsource={item.image}
itemIndex={index}
name={item.name}
/>
}
showsVerticalScrollIndicator={false}
keyExtractor={this._keyExtractor}
numColumns={2}
onEndReached={() => this._loadMoreData()}
onEndReachedThreshold={0.01}
ListFooterComponent={this._renderSearchResultsFooter}
onMomentumScrollBegin={() => this._onMomentumScrollBegin()}
/>
Voici les fonctions que j'appelle:
// Key Extractor
_keyExtractor = (item, index) => item.id;
// Check if list has started scrolling
_onMomentumScrollBegin = () => this.setState({ onEndReachedCalledDuringMomentum: false });
// Load more data function
_loadMoreData = () => {
if (!this.state.onEndReachedCalledDuringMomentum) {
this.setState({ onEndReachedCalledDuringMomentum: true }, () => {
setTimeout(() => {
if (this.state.lastLoadCount >= 20 && this.state.notFinalLoad) {
this.setState({
page: this.state.page + 1,
}, () => {
// Then we fetch more data;
this._callTheAPIToFetchMoreData();
});
};
}, 1500);
});
};
};
// Show your spinner
_renderSearchResultsFooter = () => {
return (
(this.state.onEndReachedCalledDuringMomentum && this.state.lastLoadCount >= 20 && this.state.notFinalLoad) ?
<View style={{ marginBottom: 30, marginTop: -50, alignItems: 'center' }}>
<ActivityIndicator size="large" color="#e83628" />
</View> : null
)
}
Une fois que j'obtiens des données, à l'intérieur de _callTheAPIToFetchMoreData()
, je mets à jour l'état comme ceci:
this.setState({
lastLoadCount: results.length,
onEndReachedCalledDuringMomentum: results.length >= 20 ? true : false,
notFinalLoad: results.length >= 20 ? true : false
}
Codage heureux.
La raison du déclenchement de onEndReached
plusieurs fois est due au fait que vous n'avez pas défini initialNumToRender
correctement.
onEndReached
est déclenché dans ce _ peut-êtreCallOnEndReached dans VirtualizedList.
_maybeCallOnEndReached() {
const {
data,
getItemCount,
onEndReached,
onEndReachedThreshold,
} = this.props;
const {contentLength, visibleLength, offset} = this._scrollMetrics;
const distanceFromEnd = contentLength - visibleLength - offset;
if (
onEndReached &&
this.state.last === getItemCount(data) - 1 &&
distanceFromEnd < onEndReachedThreshold * visibleLength &&
(this._hasDataChangedSinceEndReached ||
this._scrollMetrics.contentLength !== this._sentEndForContentLength)
) {
...
si contentLength
(la longueur du contenu rendu simultanément) et visibleLength
(généralement la hauteur de l'écran) est proche, distanceFromEnd
peut être très petit, donc distanceFromEnd < onEndReachedThreshold * visibleLength
peut toujours être true
. En définissant initialNumToRender
et en contrôlant la taille de contentLength
, vous pouvez éviter les appels inutiles onEndReached
.
Voici un exemple. Si vous restituez 10 éléments (il s'agit des accessoires par défaut de initialNumToRender
) de cellules de 70 px lors du rendu initial, contentLength
devient 700. Si le périphérique que vous utilisez est iPhoneX
alors visibleLength
est 724. Dans ce cas distanceFromEnd
est 24 et cela déclenchera onEndReached
sauf si vous définissez onEndReachedThreshold
inférieur à 0,03.
Il serait préférable d'utiliser onEndReached
pour définir un booléen true, puis d'utiliser onMomentumScrollEnd
en fonction de cela.
onEndReached={() => this.callOnScrollEnd = true}
onMomentumScrollEnd={() => {
this.callOnScrollEnd && this.props.onEndReached()
this.callOnScrollEnd = false
}
Cette solution a fonctionné pour moi. Ajoutez onMomentumScrollBegin et modifiez onEndReached dans FlatList Composant.
<FlatList
style = { ...}
data = {data}
initialNumToRender = {10}
onEndReachedThreshold = {0.1}
onMomentumScrollBegin = {() => {this.onEndReachedCalledDuringMomentum = false;}}
onEndReached = {() => {
if (!this.onEndReachedCalledDuringMomentum) {
this.retrieveMore(); // LOAD MORE DATA
this.onEndReachedCalledDuringMomentum = true;
}
}
}
/>
Il vous suffit de définir onEndReachedThreshold
comme un taux de visibleLength. Il vous suffit donc de le définir comme un nombre inférieur à 1. ZERO par exemple ou 0,5 alors cela devrait fonctionner !!!!!
Faites-moi savoir si cela a fonctionné pour vous.