I have the next simple page:
type Props = { getPostsUseCase: GetPostsUseCase}const renderItem = ({item}: ListRenderItemInfo<Post>) => (<NewsPost title={item.msg} />)const HomeScreen = ({getPostsUseCase}: Props) => { const [isLoading, setLoading] = useState(true) const [shouldFetch, setShouldFetch] = useState(true) const [since, setSince] = useState<number | null>(null) const [posts, setPosts] = useState<Post[]>([]) const fetchMore = useCallback(() => setShouldFetch(since != null), [since]) useEffect(() => { // Prevent fetching for other state changes if (!shouldFetch) return getPostsUseCase .execute({ limit: 15, since: since, }) .then((page: Page<Post>) => { setPosts(oldPosts => [...oldPosts, ...page.data]) setSince(page.next) }) .catch((error: string) => console.error(error)) .finally(() => { setLoading(false) setShouldFetch(false) }) }, [getPostsUseCase, shouldFetch, since]) const onEndReachedCalledDuringMomentum = useRef(true) const onEndReachedHandler = () => { if (!onEndReachedCalledDuringMomentum.current) { fetchMore() onEndReachedCalledDuringMomentum.current = true } } return (<SafeAreaView style={styles.frame}> {isLoading ? (<Text>Loading...</Text> ) : (<FlatList style={styles.list} data={posts} renderItem={renderItem} keyExtractor={item => item.id.toString()} onEndReachedThreshold={0.1} initialNumToRender={15} onEndReached={onEndReachedHandler} onMomentumScrollBegin={() => { onEndReachedCalledDuringMomentum.current = false }} /> )}</SafeAreaView> )}export default HomeScreen
However, it has two problems:
- It loads two pages on start (30 items, not 15, two requests to the backend)
- It loads entire collection multiple times on scrolling (there 30 items totally, it rendered 90)
Where is the problem? I found a lot of topics with similar problem, I tried their solutions but it didn't help (for example, this)
I am a noobie in React Native. To be honest, I don't even understand my problem and their solutions, and why they don't work.
UDP
Finally, I came up with a working solution but there is still an annoying moment:
type Props = { getPostsUseCase: GetPostsUseCase}type Data = { posts: Post[] next: number | null}const renderItem = ({item}: ListRenderItemInfo<Post>) => (<NewsPost title={item.msg} />)const HomeScreen = ({getPostsUseCase}: Props) => { const [isLoading, setLoading] = useState(true) const [data, setData] = useState<Data>({ posts: [], next: null, }) const loadData = () => { if (!isLoading && data.next == null) return getPostsUseCase .execute({ limit: 15, since: data.next, }) .then((page: Page<Post>) => { setData(oldPage => ({ posts: [...oldPage.posts, ...page.data], next: page.next, })) }) .catch((error: string) => console.error(error)) .finally(() => setLoading(false)) } // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(loadData, []) return (<SafeAreaView style={styles.frame}> {isLoading ? (<Text>Loading...</Text> ) : (<FlatList style={styles.list} data={data?.posts} renderItem={renderItem} keyExtractor={item => item.id.toString()} onEndReachedThreshold={0.1} onEndReached={loadData} /> )}</SafeAreaView> )}export default HomeScreen
ESlint requires to specify loadData
in useEffect
dependencies. I disabled that warning by // eslint-disable-next-line react-hooks/exhaustive-deps
but it's a dirty trick...
How to fix the solution to I was not needed disabling an ESLint rule?