May 9, 2017

Building a great scrollable list in React Native with FlatList

Sashko Stubailo

Sashko Stubailo

React Native enables you to use the same programming language and mental model you use to write web applications to create great native mobile experiences. Similarly, GraphQL and Apollo Client are designed to make data loading and management simple and consistent, regardless of your client platform or backend data store.

In this post I’ll show you how these two technologies can work together seamlessly in React Native’s new FlatList component. As you follow along with the post, try the live demo and even run it on your mobile device with Expo Snack: https://snack.expo.io/HJc-0uygW

Rendering lists

One of the most common UI features in a mobile app is the scrollable list. Since most apps need to display long lists of data and mobile devices don’t have much screen space, it’s important to get the right user experience. Less than two months ago, around React Conf 2017, the React Native team announced a new set of components specifically made for building these views with React: FlatList and friends.

In this article, we’ll look at the convenient APIs FlatList provides, and how they pair nicely with the features provided by GraphQL and Apollo Client. With these tools, you can build great user experiences without introducing complicated, hard-to-maintain UI or data management code.

What’s so great about React Native FlatList?

It has all of the features you need to build a great native list view, with all of the bells and whistles users expect: Infinite scroll pagination, pull to refresh, and smooth rendering performance. Here’s a minimal example from the React Native documentation:

<FlatList
  data={[{key: 'a'}, {key: 'b'}]}
  renderItem={({item}) => <Text>{item.key}</Text>}
/>

Getting a basic list displayed is simple, but how do you take advantage of all of these nice features I just mentioned? Well, it can be helpful to have a data loading library that makes loading state, refetching, and pagination easy.

Setting up FlatList and Apollo Client

Today, we’re going to focus on the props needed to set up the above features in FlatList, and see how simple it is to wire up Apollo Client to interact with them.

GraphQL API

To start, we need a GraphQL API from which we can load data. Since we’re focusing on the client we’ll just use the API from our GitHunt example app, but it’s not too hard to set up a server yourself.

Initial data

Before we can test out fancy features, we’ll need to load some initial data. With GraphQL and the react-apollo higher-order component, wiring up a query to our React component is just one function call:

const FeedWithData = graphql(
  gql`
    query Feed($pageSize: Int!, $offset: Int!) {
      feed (type: TOP, limit: $pageSize, offset: $offset) {
        repository {
          name
          owner { login }
          stargazers_count
        }
    
        postedBy { login }
      }
    }
  `,
  {
    options: {
      notifyOnNetworkStatusChange: true,
      variables: { offset: 0, pageSize: PAGE_SIZE },
    },
  }
)(Feed);

This code looks simple because people like James Baxley in the Apollo community have worked hard to make the library easy to use, but it’s actually doing a lot under the hood:

  1. Checking the cache of existing data to avoid extra data loading
  2. Keeping track of fine-grained loading status for the query
  3. Reading data out of the cache, delivering it to the Feed component, and watching for further cache updates

In this code, we configure the query with two variables — a page size we’ll use as the limit argument, and the current offset we’re looking at. To achieve pagination, we’ll later pass new values for these variables.

In addition to doing a lot of helpful bookkeeping, Apollo also adds a lot of useful information and methods to the prop it passes down. Let’s see how we can connect those to FlatList to get the features we need.

Pull to refresh

Pull to refresh is the feature that enables you to pull down on the top of a list view to get fresh data.

Try it yourself: https://snack.expo.io/HJc-0uygW

To get this, you need to pass in two props to FlatList:

  1. refreshing — A boolean that indicates whether or not a pull to refresh is currently in progress. We can easily get this from Apollo’s <a href="http://dev.apollodata.com/react/api-queries.html#graphql-query-data-networkStatus" target="_blank" rel="noreferrer noopener">networkStatus</a> prop, which provides a value of 4 when a query is currently refetching.
  2. onRefresh — A callback for when pull to refresh is activated by a user swiping down. This can be wired directly to Apollo’s refetch method, which will also set the network status for the refreshing prop above.

So, all together, it’s only two lines of code to connect Apollo and the React Native FlatList to add pull to refresh:

refreshing={data.networkStatus === 4}
onRefresh={() => data.refetch()}

And with that, we get fully native pull to refresh behavior!

Infinite scroll pagination

For application performance, it’s important to not load or display data the user isn’t looking at yet. A common technique for this is infinite scroll pagination — giving the impression of an unending list by loading new items just in time when we are about to run out of content to display:

Try it yourself: https://snack.expo.io/HJc-0uygW

Adding this is almost as simple as pull to refresh above. We just need to provide one prop to FlatList, the onEndReached callback. Here’s the code:

onEndReached={() => {
  data.fetchMore({
    variables: { offset: data.feed.length + 1 },
    updateQuery: (previousResult, { fetchMoreResult }) => {
      // Don't do anything if there weren't any new items
      if (!fetchMoreResult || fetchMoreResult.feed.length === 0) {
        return previousResult;
      }      return {
        // Append the new feed results to the old one
        feed: previousResult.feed.concat(fetchMoreResult.feed),
      };
    },
  });
}}

The callback from FlatList wires up nicely to the fetchMore method from the Apollo Client data object. It reuses the same query we saw above, but passes in a new offset variable to fetch a page of data after the items we have already seen. Then, we use a simple concat to add the items to the end, and they show up in the UI seamlessly!

Developer experience is our priority

In the Apollo community, we’re passionate about doing for data loading what React Native is doing for native UI development. We want to enable product developers to spend less time managing data and more time building a great application. Because Apollo Client includes critical boilerplate-reducing features such as network status tracking, refetching, pagination, caching, and more, building a fully-functional list view in React native on top of your GraphQL API is just a few lines of code, and that leaves you with a lot less code to worry about and maintain.


If you’re excited about developer ergonomics and you want to help build a next-generation data loading experience, please join the Apollo community and contribute, or apply for a full-time job as an open source engineer!

Written by

Sashko Stubailo

Sashko Stubailo

Read more by Sashko Stubailo