web-dev-qa-db-fra.com

FlatList onEndReached étant appelé plusieurs fois

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();
  }
}
17
Romit Kumar

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.

6
Baraka Mahili

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.

3
tomoima525

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
}
1
James Trickey

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;
    }
  }
}
/>
1
Shehzad Osama

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.

0
Helmer Barcos