0

TL;DR:

How can I safely update the Apollo client cache after a mutation if the query to update has not run and been cached (ie. right after page reload)?

Problem

The Apollo docs are not very clear on how update (safely or not) a cached query that has never been run (and therefore has no cached data to modify). Trying to use writeQuery on a query that has no cached data throws an error and obviously doesn't update the query.

  • Use try...catch during every call to writeQuery
    • This really doesn't seem like a valid approach, but might be the only way?
    • What about other errors that happen?
  • Somehow set a default state for the query cache
    • Seems like a lot of work to scaffold an app's initial state, which may prevent queries from running?

Ideas

My only ideas currently involve a try...catch around the different query update calls, but this seems like extra work. Not only that, but I will potentially be ignoring actual errors that aren't related to a query not existing already.

Example

I have two components, one for a list of the user's personal messages and the other for a list of the user's drafts. Even though all drafts are also a personal message, but I use two different queries for efficiency, as the user will likely always have far fewer drafts than sent messages.

When using the app as normal (ie. loading all queries while navigating around) everything works properly. To create a draft I need to go to Drafts.jsx (loads the Drafts query, important!) and click a button to create a draft. Upon creating the draft, I run the update function seen below (need to update both queries since their internal arguments are different). The cache update for the "Drafts" query works as expected, because the query has already been executed and data stored in the cache. However, since I have never visited the MyMessages page, the "MyMessages" query fails and throws an error.

Code

My messages query (in MyMessages.jsx)

const RECEIVED_MESSAGES_QUERY = gql`
  query GetGroup($groupSlug: String!) {
    groupEdge(groupSlug: $groupSlug) {
      node {
        id
        name
        slug
        allMessages(author: true) {
          edges {
            node {
              id
              status
              subject
              preview
            }
          }
        }
      }
    }
  }
`;

Drafts query (in Drafts.jsx):

const DRAFT_MESSAGES_QUERY = gql`
  query GetGroup($groupSlug: String!) {
    groupEdge(groupSlug: $groupSlug) {
      node {
        id
        name
        slug
        allMessages(author: true, includeStatus: ["draft"]) {
          edges {
            node {
              id
              status
              subject
              preview
            }
          }
        }
      }
    }
  }
`;

Mutation (in CreateDraft.jsx):

// Mutation file

const draftsData = proxy.readQuery({
  query: MemberDraftsQuery,
  variables: { groupSlug },
});
console.log('draftsData', draftsData);

// NOTE: Fails here (doesn't progress to next console statement)
const messagesData = proxy.readQuery({
  query: MemberMessagesQuery,
  variables: { groupSlug },
});
console.log('messagesData', messagesData);

// Add the new draft edge to the member's drafts
draftsData.groupEdge.node.allMessages.edges.push(createdDraftEdge);

// Add the new draft edge to the member's authored messages (uses different query)
messagesData.groupEdge.node.allMessages.edges.push(createdDraftEdge);

proxy.writeQuery({
  query: MemberDraftsQuery,
  data: draftsData,
  variables: { groupSlug },
});
proxy.writeQuery({
  query: MemberMessagesQuery,
  data: messagesData,
  variables: { groupSlug },
});

Related?

StackOverflow - How to update apollo cache after mutation query with filter

This article touches on the same aspect as me, but I am already handling the variables in writeQuery. However, does this mean that I should try...catch with every writeQuery operation? What if it fails for some other reason? All in all, not sure that it answers my question.

EDIT 1

After some more coding (and seen in comment below), I realized that readQuery was causing the issue (not writeQuery). Since I didn't like using try...catch because it would mix errors and not know what caused the error, I have updated my approach to make the try...catch only handle readQuery errors. If an error is thrown here, I assume that either the query didn't exist (should handle this separately) or that the cached data didn't exist. In both cases, I simply skip writing to that query.

import { MemberDraftsQuery } from '@componens/MemberDrafts';

...
class MemberDraftDetails extends Component {
  onSubmit = (groupSlug) => {
    mutate({
      variables: {
        input: {
          content,
          subject,
          ...
        }
      },
      update: (proxy, { data: responseData }) => {
        const draftEdge = responseData.createDraft;

        // UNUSED: Could be used to validate the query actually exists (doesn't warn for some reason)
        const hasDraftsQuery = Boolean(MemberDraftsQuery);

        let hasDraftsQueryCache = true;

        // A failure to read the query means that there is no cached data (therefore cannot read/write)
        //   NOTE: Could write default data, but no reason to (since it will be fetched later)
        try {
          const draftsData = proxy.readQuery({
            query: MemberDraftsQuery,
            variables: { groupSlug },
          });
        } catch (e) {
          hasDraftsQueryCache = false;
        }

        // Only add the draft to the cache if it exists. If not, it will be queried and retrieved later
        if (hasDraftsQueryCache) {
          const newDraftEdges = data.groupEdge.node.allMessages.edges.concat(draftEdge);
          data.groupEdge.node.allMessages.edges = newDraftEdges;

          proxy.writeQuery({
            query: MemberDraftsQuery,
            data,
            variables: { groupSlug },
          });
        }
      }
    }).then().catch();
  }
}
Kendall
  • 1,992
  • 7
  • 28
  • 46
  • The call that fails is `readQuery` (in you code snippet) and not `writeQuery` as you wrote. One thing you can do is write some default values to the cache in advance, and then when you call `readQuery` as you try now it won't fail. – Tal Z Apr 02 '18 at 14:00
  • Thanks, I discovered this later and figured that that's probably the best way of determining if the data is there or not. See the updated question for my new approach. I'm not a big fan of writing default values to the cache as it seems a little overboard IMO. – Kendall Apr 02 '18 at 17:46
  • I think it would have been best if `readQuery` simply returned `null` or `undefined`. Then we wouldn't need a workaround at all. But since this is the case consider this: `try ... catch` is there for every `update` and you have to remember to add it to every `update` function. Contrary to that, default values need to be written once, and all default values can potentially be handled in one place. – Tal Z Apr 02 '18 at 18:40
  • Btw, default values is the way to go according to Apollo if you use `apollo-link-state`. https://www.apollographql.com/docs/link/links/state.html#defaults – Tal Z Apr 02 '18 at 18:42
  • Ok, I may look into this then, might have a misunderstanding. Thanks! – Kendall Apr 02 '18 at 18:55

0 Answers0