1
votes

Using Apollo cache as global store - for remote and local data, is very convenient.

However, while I've never used redux, I think that the most important thing about it is implementing flux: an event driven architecture in the front-end that separate logic and ensure separation of concerns.

I don't know how to implement that with Apollo. The doc says

When mutation modifies multiple entities, or if it creates or deletes entities, the Apollo Client cache is not automatically updated to reflect the result of the mutation. To resolve this, your call to useMutation can include an update function.

Adding an update function in one part of the application that handle all cache updates; by updating queries and/or fragments for the all other parts of the application, is exactly what we want to avoid in Flux / Event driven architecture.

To illustrate this, let me give a single simple example. Here, we have (at least 3 linked components)

1. InboxCount Component that show the number of Inbox items in SideNav

query getInboxCount {
    inbox {
        id
        count
    }
}

2. Inbox list items Component that displays items in Inbox page

query getInbox {
    inbox {
        id
        items {
            ...ItemPreview
            ...ItemDetail
        }
    }
}

Both of those components read data from those GQL queries from auto generated hooks ie. const { data, loading } = useGetInboxItemsQuery()

3. AddItem Component that creates a new item. Because it creates a new entity I need to manually update cache. So I am forced to write

(pseudo-code)

    const [addItem, { loading }] = useCreateItemMutation({
        update(cache, { data }) {
            const cachedData = cache.readQuery<GetInboxItemsQuery>({
                query: GetInboxItemsDocument,
            })

            if (cachedData?.inbox) {

                // 1. Update items list GetInboxItemsQuery
                const newItems = cachedData.inbox.items.concat(data.items)
                cache.writeQuery({
                    query: GetInboxItemsDocument,
                    data: {
                        inbox: {
                            id: 'me',
                            __typename: 'Inbox',
                            items: newItems,
                        },
                    },
                })

               // 2. Update another query wrapped into another reusable method, here
                setInboxCount(cache, newItems.length)
            }
        },
    })

Here, my AddItem component must be aware of my different other queries / fragments declared in my application ????Moreover, as it's quite verbose, complexity is increasing very fast in update method. Especially when multiple list / queries should be updated like here

Does anyone have recommendations about implementing a more independent components? Am I wrong with how I created my queries?

1
In real you should use count property for both places (then one write cache needed if not requeried) - inbox (all spaces/places) should be paginated rendered with "show more" or using vitrualized listsxadm

1 Answers

1
votes

The unfortunate truth about update is that it trades simplicity for performance. A truly "dumb" client would only receive data from the server and render it, never manipulating it. By instructing Apollo how to modify our cache after a mutation, we're inevitably duplicating the business logic that already exists on our server. The only way to avoid this is to either:

  • Have the mutation return a larger section of the graph. For example, if a user creates a post, instead of returning the created post, return the complete user object, including all of the user's posts.
  • Refetch the affected queries.

Of course, often neither approach is particularly desirable and we opt for injecting business logic into our client apps instead.

Separating this business logic could be as simple as keeping your update functions in a separate file and importing them as needed. This way, at least you can test the update logic separately. You may also prefer a more elegant solution like utilizing a Link. apollo-link-watched-mutation is a good example of a Link that lets you separate the update logic from your components. It also solves the issue of having to keep track of query variables in order to perform those updates.