ユーザーがFlickr APIを使用して画像を検索できる反応ネイティブプロジェクトを作成しています。それ以外はすべて正常に機能していますが、ページネーションの実装中に発生している問題があります。 FlatListのonEndReached
を使用して、ユーザーがリストの最後までスクロールしたことを検出しましたが、問題はonEndReached
が複数回呼び出されていることです(最初のレンダリング中のものを含む)。言ったように私はバウンスを無効にしました here ですが、それはまだ何度も呼び出されています
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();
}
}
これが私が私の問題を解決した方法です:
これが私の初期状態です:
_state = {
onEndReachedCalledDuringMomentum: true,
lastLoadCount: 0,
}
_
これは私のフラットリストです
_<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()}
/>
_
これが私が呼び出している関数です:
_// 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
)
}
_
データを取得したら、_callTheAPIToFetchMoreData()
内で、次のように状態を更新します。
_this.setState({
lastLoadCount: results.length,
onEndReachedCalledDuringMomentum: results.length >= 20 ? true : false,
notFinalLoad: results.length >= 20 ? true : false
}
_
ハッピーコーディング。
onEndReached
を複数回トリガーする理由は、initialNumToRender
を適切に設定していないためです。
onEndReached
は、VirtualizedListのこの _ maybeCallOnEndReached でトリガーされます。
_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)
) {
...
contentLength
(一度にレンダリングされるコンテンツの長さ)とvisibleLength
(通常は画面の高さ)が近い場合、distanceFromEnd
は非常に小さいため、distanceFromEnd < onEndReachedThreshold * visibleLength
は常にtrue
にすることができます。 initialNumToRender
を設定してcontentLength
のサイズを制御することにより、不要なonEndReached
呼び出しを回避できます。
ここに例があります。最初のレンダリングで70ピクセルセルの10アイテム(initialNumToRender
のデフォルトの小道具)をレンダリングすると、contentLength
は700になります。使用しているデバイスがiPhoneX
の場合その場合、visibleLength
は724です。その場合、distanceFromEnd
は24であり、onEndReached
を0.03未満に設定しない限り、これによりonEndReachedThreshold
がトリガーされます。
onEndReached
を使用してブール値をtrueに設定し、それに基づいてonMomentumScrollEnd
を使用するのが最適です。
onEndReached={() => this.callOnScrollEnd = true}
onMomentumScrollEnd={() => {
this.callOnScrollEnd && this.props.onEndReached()
this.callOnScrollEnd = false
}
この解決策は私にとってうまくいきました。追加onMomentumScrollBeginおよび変更onEndReachedin FlatList成分。
<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;
}
}
}
/>
onEndReachedThreshold
をvisibleLengthの割合として設定するだけです。したがって、1未満の数値に設定するだけで済みます。たとえば、ZEROや0.5であれば、機能するはずです!!!!!
問題が解決したかどうかをお知らせください。