Rabbit
Rabbit

Reputation: 117

React Native Flatlist gets endless loop onEndReached using redux

I am using react-native FlatList component to render a list. Im fetching dummy data from an action but my pagination is not acting as expected, it's rendering everything instantly when I'm using onEndReached.

what am i missing?

check my expo https://snack.expo.io/@thesvarta/19d294

so here are my actions

  import { FETCH_FEED, MORE_FEED, REFRESH_FEED } from './actionTypes';

export const fetchFeed = () => {
  return async (dispatch, getState) => {
    const url = 'https://reqres.in/api/users?page=' + 1;
    const response = await fetch(url, {
      method: 'GET',
    });
    if (!response.ok) {
      const errorResData = await response.json();
      let message = 'error';
      throw new Error(message);
    }
    const resData = await response.json();
    dispatch({
      type: FETCH_FEED,
      data: resData.data,
      page: 1,
      rows: 1,
      message: resData.MESSAGE,
    });
  };
};
export const fetchRefreshFeed = () => {
  return async (dispatch, getState) => {
    const url = 'https://reqres.in/api/users?page=' + 1;
    const response = await fetch(url, {
      method: 'GET',
    });
    if (!response.ok) {
      const errorResData = await response.json();
      let message = 'error';
      throw new Error(message);
    }
    const resData = await response.json();
    dispatch({
      type: REFRESH_FEED,
      refresh_data: resData.data,
      refresh_page: 1,
      refresh_rows: 1,
      refresh_message: resData.MESSAGE,
    });
  };
};
export const loadMoreFeed = page => {
  return async (dispatch, getState) => {
    const url = 'https://reqres.in/api/users?page=' + page;
    const response = await fetch(url, {
      method: 'GET',
    });
    if (!response.ok) {
      const errorResData = await response.json();
      let message = 'error';
      throw new Error(message);
    }
    const resData = await response.json();
    dispatch({
      type: MORE_FEED,
      more_data: resData.data,
      more_page: page,
      more_rows: 1,
      more_message: resData.MESSAGE,
    });
  };
};

My reducers

import { FETCH_FEED, REFRESH_FEED, MORE_FEED } from '../actions/actionTypes';

const initialState = {
  data: [],
  error: null,
  page: 1,
  rows: 1,
  message: null,
};

export default (state = initialState, action) => {
  switch (action.type) {
    case FETCH_FEED:
      return {
        data: action.data,
        page: action.page,
        rows: action.rows,
        message: action.message,
      };
    case REFRESH_FEED:
      return {
        data: action.refresh_data,
        page: action.refresh_page,
        rows: action.refresh_rows,
      };
    case MORE_FEED:
      return {
        data: [...state.data, ...action.more_data],
        page: action.more_page,
        rows: action.more_rows,
      };
    default:
      return state;
  }
};

My component

import React, {
  useState,
  useEffect,
  useReducer,
  useCallback,
  getState,
} from 'react';
import { Navigation } from 'react-navigation';
import {
  StyleSheet,
  View,
  Text,
  StatusBar,
  TouchableHighlight,
  TouchableOpacity,
  FlatList,
  Image,
  Modal,
  Linking,
  ScrollView,
  Alert,
} from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import * as feedActions from '../../store/actions/feed';
const FeedScreen = props => {
  const [error, setError] = useState();
  const [refresh, setRefresh] = useState(false);
  const dispatch = useDispatch();
  const state = useSelector(state => state.feed);
  const loadFeed = useCallback(async () => {
    setError(null);
    try {
      await dispatch(feedActions.fetchFeed(state.page, state.rows));
    } catch (err) {
      setError(err.message);
    }
  }, [dispatch, setError]);

  useEffect(() => {
    loadFeed();
  }, [loadFeed]);
  const refreshFeed = useCallback(async () => {
    setError(null);
    setRefresh(true);
    try {
      await dispatch(feedActions.fetchRefreshFeed());
    } catch (err) {
      setError(err.message);
    }
    setRefresh(false);
  }, [dispatch, setError, setRefresh]);
  const morefeed = useCallback(async () => {
    setError(null);
    try {
      await dispatch(feedActions.loadMoreFeed(state.page + 1));
    } catch (err) {
      setError(err.message);
    }
  }, [dispatch, setError]);
  const renderRow = ({ item, index }) => {
    return (
      <View key={index}>
        <Text>{item.email}</Text>
      </View>
    );
  };
  if (error != null) {
    return (
      <View style={styles.centered}>
        <Text>An error occurred!</Text>
        <TouchableOpacity onPress={() => refreshFeed()}>
          Try again
        </TouchableOpacity>
      </View>
    );
  }
  return (
    <View style={styles.MainContainer}>
      <FlatList
        data={state.data}
        renderItem={renderRow}
        onRefresh={refreshFeed}
        refreshing={refresh}
        keyExtractor={(item, index) => index.toString()}
        onEndReached={morefeed}
        onEndReachedThreshold={0.5}
      />
    </View>
  );
};
const styles = StyleSheet.create({
  MainContainer: {
    flex: 1,
    backgroundColor: 'rgb(243,243,248)',
  },
});
export default FeedScreen;

Upvotes: 1

Views: 1907

Answers (1)

Afraz Hussain
Afraz Hussain

Reputation: 2413

You basically need a way to stop dispatching the loadMoreFeed action. This is the reason almost all server-sided paginations include either the next page number or the total number of pages.

I have added a simple page number check on your action:

if(page <= totalPages) {
  const url = 'https://reqres.in/api/users?page=' + page;
  const response = await fetch(url, {
    method: 'GET',
  });

  if (!response.ok) {
    const errorResData = await response.json();
    let message = 'error';
    throw new Error(message);
  }
  const resData = await response.json();
  dispatch({
    type: MORE_FEED,
    more_data: resData.data,
    more_page: page,
    more_rows: 1,
    totalPages: resData.total_pages,
    more_message: resData.MESSAGE,
  });
}

I have edited your snack with the fix, and you can find it at this link: https://snack.expo.io/rJmsZu0iS

Again, this is just a very simple method of keeping track of the number of pages. You would probably want to change it in production.

Upvotes: 1

Related Questions