I'm new with React Native and I'm trying to display a very long flatlist.
More specifically there are two arrays (a movies one and a tvs one). When I tap a specific switch I want to swap the array. So in order to make the app render the items faster while swapping "category" I've decided to use the onEndReached props and render 10 items at a time.
I'm fetching the whole two arrays during the componentDidMount method.
Now when I reach the end of the list and the 10 more items are being displayed I also want to show an ActivityIndicator.
My problem is that when I set my loading state to true and I add the other items to the displyedProducts array, then I set back again to false the loading state in order to hide the indicator, my indicator is never shown because the loading state changes too quickly although there's a full second to wait before the new elements are rendered while using the app.
Here there is the specific code block which load new items:
loadMore = () => {
const {
displayedProducts,
products,
tvs,
movies,
searchedMovies,
isSearching,
} = this.state;
const tempArray = displayedProducts;
if (
(products === 'movies' && tempArray.length === movies.length) ||
(products === 'tvs' && tempArray.length === tvs.length) ||
(isSearching && tempArray.length === searchedMovies.length)
) {
return;
}
this.setState({loading: true}, () => {
products === 'movies' &&
tempArray.push(
...movies.slice(tempArray.length, tempArray.length + 10),
);
products === 'tvs' &&
tempArray.push(...tvs.slice(tempArray.length, tempArray.length + 10));
this.setState({displayedProducts: tempArray}, () => {
this.state.loading && this.setState({loading: false});
});
});
};
I've also tried to set to false the loading state in the componentDidUpdate method but nothing changed. Do you guys know how can I solve this problem?
I'll leave here the whole component code:
import React from 'react';
import {
SafeAreaView,
FlatList,
StatusBar,
StyleSheet,
View,
ActivityIndicator,
} from 'react-native';
import {sortBy} from 'lodash';
import {getMovies, getTvs} from '../Services/MovieManager';
import Header from '../components/Header';
import MovieCard from '../components/MovieCard';
import NavigationSwitch from '../components/NavigationSwitch';
class HomeView extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
movies: [],
tvs: [],
displayedProducts: [],
searchedMovies: [],
products: 'movies',
isSearching: false,
loading: false,
};
this.flatListRef = React.createRef(null);
}
componentDidMount() {
this.fetchProducts();
}
fetchProducts = () => {
getMovies()
.then(movies => {
const moviesList = sortBy(movies.items, movie => movie.title);
this.setState({
movies: moviesList,
displayedProducts: moviesList.slice(0, 10),
});
})
.then(
getTvs().then(tvs => {
const tvsList = sortBy(tvs.items, tv => tv.title);
this.setState({tvs: tvsList});
}),
);
};
navigate = item => () => {
this.props.navigation.navigate('Movie', {movie: item});
};
renderItem = ({item}) => (
<MovieCard movie={item} onPress={this.navigate(item)} />
);
ItemSeparator = () => <View style={styles.itemSeparator} />;
getSearchedResults = searchedString => {
const {products, movies, tvs} = this.state;
let tempProducts = [];
const regEx = new RegExp(`\\b${searchedString.toLowerCase()}`);
searchedString
? this.setState({isSearching: true})
: this.setState({isSearching: false});
((products === 'movies' && movies) || (products === 'tvs' && tvs)).forEach(
product => {
product.title.toLowerCase().match(regEx) && tempProducts.push(product);
},
);
this.setState({
searchedMovies: tempProducts,
displayedProducts: tempProducts.slice(0, 10),
});
this.flatListRef.scrollToOffset({animated: true, offset: 0});
};
setList = result => {
const {movies, tvs} = this.state;
this.setState({
products: result,
displayedProducts: (
(result === 'movies' && movies) ||
(result === 'tvs' && tvs)
).slice(0, 10),
});
this.flatListRef.scrollToOffset({animated: true, offset: 0});
};
loadMore = () => {
const {
displayedProducts,
products,
tvs,
movies,
searchedMovies,
isSearching,
} = this.state;
const tempArray = displayedProducts;
if (
(products === 'movies' && tempArray.length === movies.length) ||
(products === 'tvs' && tempArray.length === tvs.length) ||
(isSearching && tempArray.length === searchedMovies.length)
) {
return;
}
this.setState({loading: true}, () => {
products === 'movies' &&
tempArray.push(
...movies.slice(tempArray.length, tempArray.length + 10),
);
products === 'tvs' &&
tempArray.push(...tvs.slice(tempArray.length, tempArray.length + 10));
this.setState({displayedProducts: tempArray}, () => {
this.state.loading && this.setState({loading: false});
});
});
};
render() {
const {displayedProducts, loading} = this.state;
return (
<>
<StatusBar barStyle="dark-content" backgroundColor="white" />
<Header
title="Home"
getSearchedResults={search => {
this.getSearchedResults(search);
}}
style={styles.header}
/>
<View style={styles.mainContent}>
<NavigationSwitch setList={this.setList} />
<SafeAreaView>
<FlatList
ref={ref => (this.flatListRef = ref)}
data={displayedProducts}
numColumns={2}
keyExtractor={item => item.id}
renderItem={this.renderItem}
columnWrapperStyle={{justifyContent: 'space-around'}}
ItemSeparatorComponent={this.ItemSeparator}
contentContainerStyle={styles.flatList}
initialNumToRender={10}
maxToRenderPerBatch={10}
onEndReached={this.loadMore}
/>
</SafeAreaView>
{loading && <ActivityIndicator style={styles.indicator} />}
</View>
</>
);
}
}
const styles = StyleSheet.create({
mainContent: {
elevation: 2,
height: '100%',
zIndex: -1,
elevation: -1,
},
flatList: {
paddingVertical: 80,
zIndex: -1,
},
itemSeparator: {
height: 25,
width: '100%',
},
indicator: {
flex: 1,
alignSelf: 'center',
bottom: 100,
elevation: 4,
},
});
export default HomeView;