ListFooterComponent 不能正常工作?

ListFooterComponent not works fine?

我对 Flatlist 有一个奇怪的问题, 当我向下滚动时,我从 API 获得了数据,我增加了页面 +1,在页脚中,我呈现了一个微调器,但是当最后一页 == 当前页面时,这意味着没有数据,但微调器卡在底部,尽管我将其更新为 false! 所以这里有什么问题!

顺便说一句 当我在 FlatList

中这样调用 renderFooter 时
ListFooterComponent={()=> this._renderFooter()} // it disappeare the bottom spiner if last page == current page but I have an unexpected re-rendering and app laggay and in some time spinner disappeared even I scrolling to bottom!!

代码

class LastSongs extends React.PureComponent {
  constructor() {
    super();
    this.state = {
      songs: [],
      loading: false,
      page: 1,
      last_page: 1,
    };
    this.isCancelled = false;
  }

  manipulateArray = async (array) => {
    let songs = [];
    array.map((track) =>
      songs.push({
        id: track.id,
        name: track.name,
        url: URL + track.sounds,
        img: URL + track.avatar,
      }),
    );
    return songs;
  };

  getData = async () => {
    try {
      this.setState({loading: true});
      let response = await API.get(`/tracks?page=${this.state.page}`);
      let lastPage = response.data.data.items.last_page;
      let {
        data: {
          data: {
            items: {data},
          },
        },
      } = response;
      let All_Songs = await this.manipulateArray(data);
      this.setState({
        songs: this.state.songs.concat(All_Songs),
        last_page: lastPage,
      });
    } catch (err) {
      console.log('err', err);
    }
  };

  _renderItems = ({item, index}) => (
    <TouchableNativeFeed
      key={item.id}
      onPress={() => {
        this.props.saveSongs(this.state.songs, index);
        this.props.isPlaying(true);
        this.props.isPauseTrigger(!this.props.isPauseTrigger);
      }}
      background={TouchableNativeFeedback.Ripple('white')}
      delayPressIn={0}
      useForeground>
      <Card
        style={{
          justifyContent: 'center',
          alignItems: 'center',
          backgroundColor: '#121212',
          flex: 1,
        }}>
        <FastImage
          source={{uri: item.img}}
          resizeMode={FastImage.resizeMode.cover}
          style={styles.cardImg}
        />
        <Body style={{...styles.cardItem, width: '100%'}}>
          <View style={styles.radioCardName}>
            <Text style={styles.text} numberOfLines={1}>
              {item.name}
            </Text>
          </View>
        </Body>
      </Card>
    </TouchableNativeFeed>
  );

  handleLoadMore = () => {
    if (this.state.page <= this.state.last_page - 1) {
      this.setState({loading: false, page: this.state.page + 1}, () =>
        setTimeout(() => {
          this.getData();
        }, 800),
      );
    } else if (this.state.page === this.state.last_page) {
      this.setState({loading: false}, () =>
        console.log('if--loading', this.state.loading),
      );
    }
  };

  _renderFooter = () => {
    return this.state.loading ? (
      <View style={styles.loader}>
        <ActivityIndicator color="#ebff64" size="large" />
      </View>
    ) : null;
  };
  componentDidMount() {
    this.getData();
  }

  _keyExtractor = (song) => song.id;

  _renderListEmptyComponent = () => <EmptyList />;
  render() {
    console.log('App rerender!');
    return (
      <Container style={styles.container}>
        <Header
          style={styles.darkHeader}
          androidStatusBarColor="#121212"
          iosBarStyle="light-content">
          <Left>
            <Button transparent onPress={() => this.props.navigation.goBack()}>
              <Icon name="ios-arrow-forward" style={styles.colorWhite} />
            </Button>
          </Left>
          <Body>
            <Title style={styles.headerText}>اخر الاغاني</Title>
          </Body>
          <Right></Right>
        </Header>
        <FlatList
          data={this.state.songs}
          keyExtractor={this._keyExtractor}
          initialNumToRender={10}
          contentContainerStyle={styles.contentContainerStyle}
          columnWrapperStyle={styles.columnWrapperStyle}
          numColumns={2}
          ListEmptyComponent={this._renderListEmptyComponent}
          renderItem={this._renderItems}
          onEndReached={this.handleLoadMore}
          onEndReachedThreshold={0.7}
          ListFooterComponent={this._renderFooter}
          removeClippedSubviews={true}
          maxToRenderPerBatch={100} // Increase time between renders
        />
      </Container>
    );
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    isPlaying: (_isPlaying) => {
      dispatch(isPlayingAction(_isPlaying));
    },

    isPauseTrigger: (_isPause) => {
      dispatch(isPauseAction(_isPause));
    },

    saveSongs: (songs, index) => {
      dispatch(saveSongsPlayer(songs, index));
    },
  };
};

export default connect(null, mapDispatchToProps)(LastSongs);

您在滚动到底部时调用了超时的异步函数。该超时将覆盖您的以下代码并再次将加载设置为 true。所以在这种情况下加载永远不会是错误的。

 } else if (this.state.page === this.state.last_page) {
      this.setState({loading: false}, () =>
        console.log('if--loading', this.state.loading), // log false!! that's mean a spinner should disapeare
      );
 }

这里需要两样东西。

1) 尝试在您的 catch 块中将 loading 设置为 false。

} catch (err) {
   console.log('err', err);
   this.setState({loading: false});
}

2) 在您的状态 isAllDataFetched 中添加另一个初始值为 false 的值。当您从 API 收到空数据时,将加载设置为 false。不确定您的数据是什么样子,但可以做类似的事情;

getData = async () => {
    try {
      this.setState({loading: true});
      let response = await API.get(`/tracks?page=${this.state.page}`);
      let lastPage = response.data.data.items.last_page;
      let {
        data: {
          data: {
            items: {data},
          },
        },
      } = response;

      // if it's an array
      if(data.length === 0) {
         this.setState({loading: false, isAllDataFetched: true});
      }
      //...
    } catch (err) {
      console.log('err', err);
    }
  };

最后,在您的 handleLoadMore 方法中添加以下行。

handleLoadMore = () => {
 if(this.state.isAllDataFetched) return;

我为您创建了一个演示。您可以按照此逻辑使其工作。它与您拥有的有点不同,但我认为它会有所帮助。

这是代码。

import React from 'react';
import {
  View, Text, FlatList, ActivityIndicator, SafeAreaView
} from 'react-native';


class App extends React.PureComponent {
  state = {
    songs: [
      {
        userId: 1,
        id: 1,
        title: 'delectus aut autem 1',
        completed: false,
      },
      {
        userId: 1,
        id: 2,
        title: 'delectus aut autem 2',
        completed: false,
      },
    ],
    loading: false,
    page: 3,
    totalPage: 10,
  };

  componentDidMount() {
    this.getData();
  }

  getData = async () => {
    const { songs } = this.state;
    try {
      this.setState({ loading: true });
      const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${this.state.page}`, {
        headers: {
          'Content-Type': 'application/json',
        },
      });
      const json = await response.json();

      this.setState({
        songs: [...songs, json],
      });
      this.setState({ loading: false });
    } catch (err) {
      console.log('err', err);
      this.setState({ loading: false });
    }
  };

  renderItems = ({ item }) => (
    <Text style={{ backgroundColor: 'blue', height: 200, marginBottom: 5 }}>{`${item.title}-${item.id}`}</Text>
  );

  onEndReached = () => {
    const { page, loading, totalPage } = this.state;

    if (loading) return;

    if (page <= totalPage) {
      this.setState({ loading: true, page: page + 1 }, () =>
        setTimeout(() => {
          this.getData();
        }, 2000));
    } else {
      this.setState({ loading: false });
    }
  }

  renderFooter = () => {
    const { loading } = this.state;

    if (loading) {
      return (
        <View>
          <ActivityIndicator color="#000" size="large" />
        </View>
      );
    }
    return null;
  }

  renderListEmptyComponent = () => <View />;

  render() {
    const { songs } = this.state;
    return (
      <SafeAreaView style={{ flex: 1, backgroundColor: 'red' }}>
        <FlatList
          data={songs}
          keyExtractor={song => song.id}
          initialNumToRender={10}
          contentContainerStyle={{ flexFrow: 1, backgroundColor: 'white' }}
          style={{ flex: 1 }}
          ListEmptyComponent={this.renderListEmptyComponent}
          renderItem={this.renderItems}
          onEndReached={this.onEndReached}
          onEndReachedThreshold={0.7}
          ListFooterComponent={this.renderFooter}
        />
      </SafeAreaView>
    );
  }
}
export default App;

并且提供了工作演示 here(使用 iOS 设备)