ListFooterComponent не работает нормально? - PullRequest
0 голосов
/ 06 апреля 2020

У меня странная проблема с Flatlist, я получаю данные из API, когда я прокручиваю вниз, я увеличиваю страницу +1, а в нижнем колонтитуле я рисую счетчик, но когда последняя страница == текущая страница, то есть данные не приходят но спиннер застрял внизу, хотя я обновляю его до false! так что тут не так!

Кстати, когда я вызываю renderFooter, как этот способ в FlatList

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!!

Code

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);

1 Ответ

1 голос
/ 06 апреля 2020

Вы вызываете асинхронную функцию c с таймаутом, пока вы прокручиваете до конца. Это время переопределит ваш следующий код и снова установит загрузку в 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
      );
 }

Вам нужно 2 вещи здесь.

1) Попробуйте установить загрузку в false в вашем блоке catch.

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

2) Добавьте другое значение в вашем штате isAllDataFetched с начальным значением false. Установите загрузку в false, когда вы получаете пустые данные от вашего API. Не знаю, как выглядят ваши данные, но сделайте что-то вроде:

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;

Я создал демо для вы. Вы можете следовать этой логике c, чтобы заставить ее работать. Это немного отличается от того, что у вас есть, но я думаю, что это поможет.

Вот код.

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;

И рабочая демоверсия доступна здесь (используйте устройство iOS)

...