mediumExtra#96

GraphQL query with Apollo Client

Prompt

Write a React Native component that uses Apollo Client's useQuery hook to fetch a list of books (id, title, author) and display them — handling the loading and error states before touching data.

Solution

export default function BookList() {
  const { loading, error, data } = useQuery(GET_BOOKS)

  if (loading) return <Text>Loading...</Text>
  if (error) return <Text>Error: {error.message}</Text>

  return (
    <FlatList
      data={data.books}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <Text>{item.title} by {item.author}</Text>
      )}
    />
  )
}
Mentor's take

useQuery is declarative data fetching: the component states what it needs and Apollo owns the lifecycle — request, cache, dedupe, re-render. The gql tag is a tagged template — a function receiving the string parts and interpolated values separately — which parses the query into an AST once at module load, not per render. That's the same mechanism behind styled-components, and naming it earns points.

The three-state ladder — loading, then error, then data — isn't boilerplate, it's the contract: touch data.books before checking the first two and the first slow network turns into a crash on undefined.

The senior detail is why the query selects id. Apollo's cache is normalized: objects are stored flat, keyed by __typename plus id. Query the id and a mutation returning the same book updates every screen showing it automatically; omit it and the cache can't normalize, so updates silently stop propagating. keyExtractor reusing that id is the free by-product, not the reason.

Also worth saying: loading is only true on the first fetch by default — refetches need notifyOnNetworkStatusChange if you want a spinner — and error can coexist with partial data.

Red flag: mirroring data into local state with useEffect + useState. The normalized cache is the state; the copy goes stale the moment a mutation updates the original.

Say it: "useQuery gives me the three render states declaratively, and because Apollo normalizes by __typename plus id, selecting the id is what makes cache updates propagate — not just a keyExtractor convenience."