Refetch Strategies for Apollo’s GraphQL client
By: Jeff Lowery
Tags: GraphQL, Apollo Client

Refetch Strategies for Apollo’s GraphQL client

There are several techniques for refetching data. In this article, we explore them by example.

The setup

In Giving React + GraphQL a Lift with Apollo-Boost, I demonstrated how to get React application up and running quickly, and then how to incorporate the ApolloClient React components from apollo-boost. At the time of that writing, I used the Apollo Launchpad service to create a GraphQL server instance, but that has now met its demise. This time I’ll install and run the GraphQL server from the Apollo GraphQL Tutorial. Here are the steps to get that server running:



cd fullstack-tutorial/final/server

npm install

npm run-script start:ci git clone https://github.com/apollographql/fullstack-tutorial/ cd fullstack-tutorial/final/servernpm installnpm run-script start:ci

With success, you’ll see:

$ npm run-script start:ci

... 🚀 app running at http://localhost:4000/

The server will have to be running locally for the various branches of the application source I’m about to present to function.

The App

I’m ignoring the client that comes with the Apollo tutorial and substituting my own. For this article, I’ll iterate through several branches of my GitHub repository, with links to each at the end of this post.

Before getting started, take a look first at the GraphQL schema at http://localhost:4000 in your browser:

From the Playground, I can query launches :

So far, so good. Now it’s time to build an app. In the previously mentioned article, a React application was generated by create-react-app. Here, I’ll do the same:

npx create-react-app apollo-refetch

cd my-app

npm start

Once everything is loaded and the server is running, you’ll see a spinning graphic of an atom. Nice, but not useful. Before I can replace that graphic, I need to install GraphQL along with Apollo’s react tools:

npm i apollo-boost graphql react-apollo -S

Now we’re ready to go.

First step: the Query component

I’ll first create a component called Launches. This will replace the innards of the App component found in src/App.js of the apollo-boost generated app.

This yields (with the help of some stolen CSS):

This initial page will be a read-only view. I added a button that will navigate to an editor page so that changes can be made to the launch list.

Next step: the Editor component

The Editor component looks a lot like the Launches list, but with a control for booking on or more launches.

The Editor code has a query much like the one for the Launches page, and two mutations, one for login and the other to update mission bookings.

The server application from the full-stack tutorial requires a login before authorizing bookings. For my purposes, I will just generate a random login email from faker, perform the login, get the auth key, and then make sure my Apollo client instance is aware of it for subsequent requests:

Full source code links at bottom of this post.

It has some problems. If you go to the editor page, book a flight or two, then click submit, it may seem okay. However, the page should show only non-booked flights, so the flights you book should disappear from the list once the mutation has executed. Also, if you go back to the start page, the flights will still show as unbooked. The problem is that mutations don’t refetch data without you telling them so, and without a refetch, the React components won’t re-render.

There are several techniques for refetching data, which is what the rest of this article is about.

query/refetch

The simplest scenario for refetch is when there is component that both queries and mutates. The component first queries to populate, say, a list of items; then there is a control within the component that triggers a mutation operation, such as adding/deleting/modifying one or more of those items. Once the mutation is performed, the component is updated by refetching the data.

The Editor is just such a component:

The Query subcomponent (exported from apollo-client) wraps itself around the Submit component shown previously, which contains a Mutate component.

In this case, if I want to update the List so that it no longer shows the flights I just booked, I can use a refetch function that the Query component provides:

<Query query={LAUNCHES}>

{({ data, loading, error, refetch }) => {

...

<Submit refetch={refetch}/>

Now, when Submit performs the mutation, it can refetch afterwards:

const submitChanges = (evt, mBook, refetch) => {

const bookingElems = document.getElementsByName('booking')

const bookedAry = []; if (bookingElems.length) {

bookingElems.forEach(booked => {

if (booked.checked)

bookedAry.push(booked.id)

});

mBook({variables: {ids: bookedAry}})

refetch();

}

} const Submit = (props) => {

const {refetch} = props;

... return <Mutation mutation={BOOK}}>

{mBook => (

<input type="submit" value="Submit Changes"

onClick={evt => submitChanges(evt, mBook, refetch)} />

)}

</Mutation>

All well and good, but the Query must contain the Mutation for this to work. True, you could store the refetch method returned from Query in some React state structure or a React hook, but there’s another way.

refetchQueries

The Mutation itself can direct the refetching of queries, which in turn will update components displaying query results.

Mutation refetch by query name

There are two named queries in the application: ls and bookie . The ls query is used on the Launches page and the bookie query on the Editor page.

// Launches.js

const qLaunches = gql`query ls {

launches {

launches {

id

site

mission {

name

}

rocket {

name

type

}

isBooked

}

}

}`; // Editor.js

const LAUNCHES = gql`

query bookie {

launches {

launches {

id

site

mission {

name

}

rocket {

name

type

}

isBooked

}

}

}`;

Notice the similarities? Because they are essentially the same query under different guises, Apollo’s caching will make some of what I will demonstrate redundant, but the concepts are sound.

Instead of passing refetch around like a hot potato, I can instead have the Mutation component call the queries I want to update after the mutations are done.

const Submit = (props) => {

...

return <Mutation mutation={BOOK} refetchQueries = {['ls', 'bookie']}>

{mBook => (

<input type="submit" value="Submit Changes"

onClick={evt => submitChanges(evt, mBook)} />

)}

</Mutation>

And this will update both the Editor and Launches pages. (Actually, updating one updates the other, on account of that caching thing I mentioned.)

Mutation refetch by full query with query variables

The above technique is fine so long as I don’t have variables I have to pass to the query, but if I do, there is another syntax:

... refetchQueries = {[{

query: ['GetCollectionByUserId'],

variables: { userId : userId, first : :"Joe", last : "Friday"

}]}

If you’re not familiar with GraphQL query variables, there is an example here.

The one problem with the refetchQueries technique is that it can be hard to determine what mutations affect which queries. If I were to write a new Mutation component for a large project, would I know which Queries are affected?

Polling

Another technique for keeping Query component fresh is through polling. A Query component can take a pollInterval property, in milliseconds.

export default function Launches(props) {

const {setEditing} = props; return (

<Query query={qLaunches} pollInterval={1500}>

{({ data, loading, error }) => {

if (loading) return <p>Loading...</p>;

if (error) return <p>ERROR</p>;

return (

<Fragment>

<input type='button' value='make changes' onClick={() => setEditing(true)}/>

<table style={{ border: 'solid 1px' }}>

<tbody>

<tr>

<th>Site</th><th>Mission</th><th>Rocket</th><th>Booked</th>

</tr>

{launchRows(data)}

</tbody>

</table>

</Fragment>

);

}}

</Query>

);

};

The Query component is then refreshed every so often. This can be real handy if monitoring real-time events, but a lot of polling operations can affect network performance.

Subscriptions

Finally, subscriptions are available in GraphQL, and I’ve written about them before. If polling doesn’t appeal to you, and wiring up Queries and Mutations together seems potentially hard to maintain, then having a subscription service that refreshes Queries (by calls the refresh() method as shown previously) when a subscribed-to event occurs may have its advantages.

A bit of wiring would need to be set up to accomplish this: basically a mapping of subscribe events to Query refresh() methods. There might be a reduction in network traffic compared to polling, though the trade-off is the increased socket connections to the server or servers.

Source code