Implementing pagination for Airtable data with React Native FlatList scroll loading

I’m working on a React Native app that fetches data from Airtable. The issue is that Airtable’s API returns a maximum of 100 records per request by default. I need to implement pagination so that when users scroll to the bottom of my FlatList, more records get loaded automatically.

I’ve tried using the offset parameter from Airtable’s response, but my current implementation loads all available records at once instead of loading them in smaller chunks as the user scrolls.

Here’s my current approach:

const API_TOKEN = "your_api_token";
const DATABASE_ID = "your_base_id";
const SHEET_NAME = "MainTable";
const VIEW_TYPE = "Default view";
const BATCH_SIZE = 15;
const database = new Airtable({ apiKey: API_TOKEN }).base(DATABASE_ID);

const Dashboard = () => {
  const [dataList, setDataList] = useState([]);
  const [loading, setLoading] = useState(false);
  const [nextOffset, setNextOffset] = useState(null);

  useEffect(() => {
    fetchInitialData();
  }, []);

  async function fetchInitialData() {
    setLoading(true);
    await fetchMoreData();
  }

  async function fetchMoreData() {
    setLoading(true);

    const result = await database(SHEET_NAME)
      .select({ 
        view: VIEW_TYPE, 
        pageSize: BATCH_SIZE, 
        offset: nextOffset 
      })
      .all();

    const combinedData = [...dataList, ...result];
    setDataList(combinedData);

    if (result.offset) {
      setNextOffset(result.offset);
    } else {
      setNextOffset(null);
    }

    setLoading(false);
  }

  function displayItem({ item }) {
    return <ImageCard source={item.fields.image} />;
  }

  function showLoadingIndicator() {
    return loading ? (
      <View style={styles.loadingContainer}>
        <ActivityIndicator size="large" color="#0000ff" />
      </View>
    ) : null;
  }

  function onScrollEnd() {
    if (!loading && nextOffset) {
      fetchMoreData();
    }
  }

  return (
    <View style={styles.mainContainer}>
      <FlatList
        data={dataList}
        renderItem={displayItem}
        numColumns={2}
        keyExtractor={(record) => record.id}
        ListFooterComponent={showLoadingIndicator}
        onEndReached={onScrollEnd}
        onEndReachedThreshold={0.1}
        initialNumToRender={BATCH_SIZE}
      />
    </View>
  );
};

export default Dashboard;

The problem is that instead of loading records in batches when scrolling, everything loads at once. What am I missing in my pagination logic?

Yeah, .all() is definitely the issue. But also check your onScrollEnd - you’ll want debouncing since onEndReached can fire multiple times super fast and cause duplicate requests. Add an isFetching flag to prevent overlapping calls. That 0.1 threshold is way too aggressive too - bump it to 0.5 so it triggers earlier.

You’re using .all() which grabs everything and ignores your pagination setup. It keeps fetching until it gets all records, no matter what pageSize you set.

Swap .all() for .firstPage() on the initial load:

async function fetchInitialData() {
  setLoading(true);
  
  const result = await database(SHEET_NAME)
    .select({ 
      view: VIEW_TYPE, 
      pageSize: BATCH_SIZE
    })
    .firstPage();

  setDataList(result.records);

  if (result.offset) {
    setNextOffset(result.offset);
  }

  setLoading(false);
}

For loading more data, same deal - use .firstPage() with the offset:

async function fetchMoreData() {
  if (!nextOffset) return;
  
  setLoading(true);

  const result = await database(SHEET_NAME)
    .select({ 
      view: VIEW_TYPE,
      pageSize: BATCH_SIZE,
      offset: nextOffset
    })
    .firstPage();

  setDataList(prev => [...prev, ...result.records]);
  setNextOffset(result.offset || null);
  setLoading(false);
}

Hit this exact issue last year building something similar. .all() is just a helper that keeps hammering the API until there’s nothing left - totally defeats pagination.

Also, throw some error handling around these calls. Airtable gets flaky and you’ll crash without try/catch blocks.

Your offset handling has a bug. You’re passing nextOffset (which starts as null) to fetchInitialData(), then immediately calling fetchMoreData() with that null offset. This breaks the pagination flow.

Split your initial load from subsequent loads completely. Don’t call fetchMoreData() from inside fetchInitialData(). Also, Airtable’s .firstPage() returns result.records for the data and result.offset for pagination - you’re missing the .records part when setting state.

I hit this exact issue last month building something similar. Airtable’s offset system is finicky - you need to be explicit about first fetch vs paginated fetches. Also check result.records.length before appending data, or you’ll get empty batches that screw up your loading states.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.