Update Apollo Cache after a mutation and get instant benefits on your UI
Tags: apollo, graphql, cache, ui
Published: 2020-01-17 16:12:27

I'm very fond of the products developed by the Apollo team, specially the **Apollo Client**. It started as a simple tool to connect your frontend data requirements with a GraphQL server but, nowadays, it's a **complete solution for data management** on the client-side. Even though using GraphQL [potentially reduces the need for local state](https://medium.com/@wmdmark/how-graphql-replaces-redux-3fff8289221d), Apollo comes with a redux-like "global" store that you access it with a GraphQL interface. If you want to know more about how to implement **client-state with Apollo**, read [here](https://www.apollographql.com/docs/react/data/local-state/).

I don't want to talk about local resolvers or frontend-only state, but the underlying cache that Apollo uses when you fetch GraphQL from the server, specially how you can leverage it to make your **UIs more responsive** and *smart*. Apollo docs on how to [interact with cache](https://www.apollographql.com/docs/react/caching/cache-interaction/) is a good resource but, IMO, a bit far from real use cases.

## Simple CRUD 

I'll create a simple Wishlist React app (though most of what is shown is applicable to other frameworks), that is simply a CRUD of **Items** `{ title: string, price: int }`. Like most real apps it has **a list of resources**, a **delete button**, a **edit form** and an **add form**.What I want to show is, basically, **how to update the list after a mutation** without having to refetch the list from the server. 

> I'll be using [this](https://codesandbox.io/s/simple-apollo-server-wishlist-kcn0g) simple server that I've created with **Codesandbox** _(man, aren't these guys awesome?)_ and the only thing to notice is that, on the _edit_ mutation, your server **should return the updated resource**, you'll know why later. I'll add some mock data for us to see the results right-away.

I'll assume that you already know how to setup an Apollo Client + React Apollo application. If not, check it [here](https://www.apollographql.com/docs/react/get-started/).

So, I'll go over every letter on _CRUD_ (for educational purposes, RUCD) and explain what you may need to do to use.

### Read

```jsx
const GET_ITEMS = gql`
  {
    items {
      id
      title
      price
    }
  }
`;

function Wishlist() {
  const { loading, error, data } = useQuery(GET_ITEMS);

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

  return data.items.map(item => {
    const { id, title, price } = item;
    return (
      <div key={id}>
        {title} - for ${price} -{" "}
      </div>
    );
  });
}

```


This is it! Our **read query** is very simple: it doesn't take search parameters, pagination metadata or uses cursors. For demonstration purposes it's better this way, but I'll comment later if you need some help with more complex cases. Just with this setup, we're already prepared for our list to _react_ to cache changes on subsequent writes.

![img](https://i.imgur.com/ZPPeBkA.png)

**Note:** Using `useQuery`, `<Query>` or even `withQuery` is fundamental here, since it **links the data with the cache**. If you use other solution like `useApolloClient` and, then, `client.query`, these solutions will not work for you.

### Edit

We will create our Edit component and this one is the easiest case because **you don't need to do anything** for the list to auto update after you edit the Item. Apollo Cache, by default, uses the pair `__typename` + `id` (or `_id`) as a primary key to every cache object. **Every time the server returns data, Apollo checks if this new data replaces something that it has on its cache**, so, if your `editItem` mutation returns the complete updated `Item` object, Apollo will see that it already has that item on the cache (from when you fetched for the list), update it, and triggers the `useQuery` to update the `data`, subsequently making our `Wishlist` re-render.

```jsx 
const EDIT_ITEM = gql`
  mutation($id: Int, $item: ItemInput) {
    editItem(id: $id, item: $item) {
      id
      title
      price
    }
  }
`;

const EditItem = ({ item: { title, price, id } }) => {
  const [editItem, { loading }] = useMutation(EDIT_ITEM);

  return (
    <ItemForm
      disabled={loading}
      initialPrice={price}
      initialTitle={title}
      onSubmit={item => {
        editItem({
          variables: {
            id,
            item
          }
        });
      }}
    />
  );
};
```

Done! When the user hit the `Submit` button on the edit form, the mutation will trigger, the Apollo client will receive the updated data and **that item will update automatically** on the list.

### Create

Let's create our `AddItem` component. This time it's a bit different because when we get our response from the server (the newly created `Item`) Apollo doesn't "know" that the list should be updated with the new item (and, sometime, it shouldn't). For this we have to **programatically add our new item to the list**, and one of the parameters of the `useMutation` hook is the `update` function, that is there specifically for that purpose.

The steps we need to update the cache are:
1. Read the data from Apollo cache (we will use the same `GET_ITEMS` query)
2. Update the list of items pushing our new item
3. Write the data back to Apollo cache (also referring to the `GET_ITEMS` query)

After this, Apollo Client will notice that the cache for that query has changed and will also update our Wishlist on the end.

```jsx 
const ADD_ITEM = gql`
  mutation($item: ItemInput) {
    addItem(item: $item) {
      id
      title
      price
    }
  }
`;

const AddItem = () => {
  const [addItem, { loading }] = useMutation(ADD_ITEM);

  return (
    <ItemForm
      disabled={loading}
      onSubmit={item => {
        addItem({
          variables: {
            item
          },
          update: (cache, { data: { addItem } }) => {
            const data = cache.readQuery({ query: GET_ITEMS });
            data.items = [...data.items, addItem];
            cache.writeQuery({ query: GET_ITEMS }, data);
          }
        });
      }}
    />
  );
};
```

### Delete

The delete case is very similar to the create-one. I will colocate it on the Wishlist component for simplicity, and also add some props/state for the overall functionality of the app.

```jsx
const DELETE_ITEM = gql`
  mutation($id: Int) {
    deleteItem(id: $id) {
      id
      title
      price
    }
  }
`;

function Wishlist({ onEdit }) {
  const { loading, error, data } = useQuery(GET_ITEMS);
  const [deleteItem] = useMutation(DELETE_ITEM);

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

  return data.items.map(item => {
    const { id, title, price } = item;
    return (
      <div key={id}>
        {title} - for ${price} -{" "}
        <button
          className="dim pointer mr2"
          onClick={e => {
            onEdit(item);
          }}
        >
          edit
        </button>
        <button
          className="dim pointer"
          onClick={() => {
              deleteItem({ variables: { id }, 
              update: cache => {
                const data = cache.readQuery({ query: GET_ITEMS });
                data.items = data.items.filter(({id: itemId}) => itemId !== id);
                cache.writeQuery({ query: GET_ITEMS }, data);
              }});
          }}
        >
          delete
        </button>
      </div>
    );
  });
}

```

## Advanced cases

It's common to have more complex lists on our apps with **search**, **pagination** and **ordering** and, for those, it gets a bit complicated and it heavily depends on the context of your application. For example, when deleting an item of the 4th page of items, should we delete it from the UI and show `pageLength - 1` items or fetch one item from the next page and add it?

These cases are also tricky because the `cache.readQuery` also need variables if the `query` provided receives it, and you might not have it globally available.  One option is using something like [`refetch`](https://www.apollographql.com/docs/react/data/queries/#refetching) from the original query, or [`refetchQueries`](https://www.apollographql.com/docs/react/data/mutations/) from Apollo, what makes it go on the server again. If you want to dig deeper on this problem, [this issue](https://github.com/apollographql/react-apollo/issues/708) has a lot of options for you, specially a [snippet](https://gist.github.com/ngryman/6856c7eb8f9a15b1095032a6ba478c5c) for getting a query's last used variables.

## The final solution

> The app has some state/UI quirks, I've made the minimal to demonstrate Apollo Cache Update :)

The whole app developed here is on [this CodeSandbox](https://codesandbox.io/s/apollo-cache-wishlist-app-p4g94). You might need to fork [this](https://codesandbox.io/s/simple-apollo-server-wishlist-kcn0g) container and update the server URL.
{% codesandbox basic-apollo-app-p4g94 %}
![img](https://i.imgur.com/7fxMnEa.png)


Feel free to reach me :)