How to handle cache update after mutation in multiple views? - reactjs

This is more of a open question but hopefully it won’t get deleted.
I am using react and apollo although the question is more general.
Let’s say I have 3 distinct views in my app all using similar (but not the same) data.
All of them are using separate queries but each of the query uses common operation but with slightly different data returned.
Let’s say I have a mutation somewhere that adds something to data (think of a list of items and a new item being added).
Let’s say after mutation I want to update cache to reflect that change. I am using read/writeQuery to do the update.
With this setup I need to update 3 queries - this becomes a maintenance nightmare.
After some reading I figured I am doing this wrong - I have now created a single query - now I need to only update that single query after mutation and all of my views are updated automatically.
However the problem is that this query now has to download all the data that all 3 views combined need - feels like this is very inefficient, because some of the views will get data they'll never use.
Is there a better way to do it?
Please note that read/writeFragment won't work because they won't update the underlying queries - check this answer for example: https://stackoverflow.com/a/50349323/2874705
Please let me know in comment if you need a more concrete example.
All in all I think in this setup I would just be better with a global state handling and avoid apollo cache all together - however I feel cheated cause apollo promised to solve the state problems :)
EDIT
Here's a concerete example:
Let's say our graphql schema is defined liked this:
type Post {
id: ID!
title: String!
body: String
published: Boolean!
}
type Query {
posts(published: Boolean): [Post!]!
}
type Mutation {
createDraft(body: String!, title: String!): Post
publish(id: Int!): Post
}
Now, we create 3 queries and 2 mutations on the client
query PostTitles {
posts {
id
title
}
}
query Posts {
posts {
id
title
body
published
}
}
query PublishedPosts {
posts (published: true) {
id
title
body
published
}
}
mutation CreateDraftPost ($body: String!, $title: String!) {
createDraft(body: $body, title: $title) {
id
title
body
published
}
}
mutation PublishPost ($id:ID!) {
publish (id: $id) {
id
published
}
}
Just to note createDraft creates a post with the default false published value.
How can use either of those mutations to create or publish a post and have all the 3 cached queries to be updated without using refetchQueries or manualy updating each of the query?
I think the real problem is that each of those queries are stored separately in the apollo in-memory cache.

From my experience, here's how it should goes.
In the case of CreateDraftPost mutation:
You call the mutation and also pass an update function. In this update function, you modify the cache of the root query posts by creating a new fragment of Post and then add this fragement into posts. See this: https://www.apollographql.com/docs/react/data/mutations/#making-all-other-cache-updates
Since the PostTitles and Posts all rely on the root query posts (just differ in the queried fields) and the new fragment of Post you've just added into posts has sufficient fields, your PostTitles and Posts should automatically reflect the changes.
Since CreateDraftPost always create a draft with published defaults to false. You don't need to update anything related to PublishedPosts query.
In the case of PublishPost mutation:
You call the mutation and the returned result is a Post with updated fields (id, published). By the mechanism of Apollo GraphQL cache, this Post (identified by id) will be updated in any queries it has involved. See this: https://www.apollographql.com/docs/react/data/mutations/#updating-a-single-existing-entity
However, you need to manually update the PublishedPost query. Do this by providing update function in the mutation call. In this update function, you will readQuery of PublishedPost first, create a new Post out of the returned data and finally writeQuery to add this post into the PublishedPost results. Reference this: https://www.apollographql.com/docs/react/caching/cache-interaction/#combining-reads-and-writes
How about using refetchQueries:
In the case of CreateDraftPost mutation, refetch only Posts query should be sufficient (the PostTitles should be updated accordingly) since both Posts and PostTitles rely on the same root query posts and fields in Posts has also covered fields in PostTitles
In the case of PublishPost mutation, I would prefer refetch the PublishedPost query to avoid doing the whole update thing (since I'm lazy and I think it will not cost me much to refetch 1 query)

It sounds like you've looked into and used the update argument that can be passed to mutation functions returned from useMutation. You're probably using proxy.readQuery and proxy.writeQuery to update it (or letting this magic happen in the background). If not, here is the documentation.
Another approach that is similar in concept but finer detail is to use proxy.readFragment and proxy.writeFragment. You can specify a set of properties on a type as being part of a fragment, and update that fragment whenever new data comes in. The nice part is that this fragment can be used within any number of queries, and if you update the fragment, those queries will update.
fragment documentations

Related

Apollo Client fetching data from cache when it should not

I'm having trouble understanding a weird behaviour when using Apollo Client.
Here is a little bit of context, maybe I missed something:
I have an app that displays lists of items.
Thanks to routing, each list has its own route :
/list/:listId
Each list has its own settings, and these settings are used to know which item details are displayed (per se, on one list I can decode to show the price of my item, and hide it on another list)
Now, the problematic part: I'm using the apollo client useQuery hook to fetch my list. the listId is obtained through the react-router-dom useParams hook, and then passed as a variable to useQuery, and that's my whole point:
useQuery(ITEM_LIST, {
variables: {
listId,
}
})
Even when passing a new listId by navigating from a list to another, apollo still fetches the settings from my previous list, for a very short moment, resulting in a glitchy user experience (details that should not be displayed are displayed for a moment, then immediately disappear)
I read a few articles regarding apollo caching issues, such as this one, but still, didn't manage to find the source of this problem. Here are the things I've tried so far:
alias the id field in my query
make sure my server returned a response with a unique ID
Has anyone seen this before?

When using React Query how do I make sure data is normalized across queries of a dataset?

I'm writing an sort of project management app that'll have a large number of "tasks" with a lot of properties within them that'll be rendered throughout the app. I am looking at using React Query to prefetch, cache locally, and update this data from the server.
One key architectural thing I want to get right is that when I query or mutate Tasks[123] that I affect a single underlying object in the state and not get stuck with duplicate data everywhere. On first glance React Query seems to be perfect for this job if the Query Keys are setup right. However in their examples they don't seem to do this (or I'm failing to understand).
In their Basic Example they fetch some Posts on start and query using queryClient.getQueryData(["post", post.id]). As far as I can tell this is causing the data to be duplicated if I look at the provided ReactQueryDevtools window in the example.
Am I correct in thinking the example should be rewritten to use something like queryClient.getQueryData(["posts", {id: post.id} ])?
That is indeed the way I am setting up my query keys, so that I can do: queryClient.invalidateQueries(['posts']) and it invalidates all of them. But sometimes, you need more fine granular control. If that's the case, I'd do:
["posts", "list", { filter: "all" }]
["posts", "list", { filter: "published" }]
["posts", "detail", 1]
["posts", "detail", 2]
that way, I can still tackle everything with ["posts"], all lists with ["posts", "list"], all details with ["posts", "detail"] and a specific detail with ["posts", "detail", id] etc.
It is also good practice to have a queryKeyFactory to create those keys, something like:
const postKeys = {
prefix: "posts",
lists: [postKeys.prefix, "list"],
list: (filter) => [...postKeys.lists, { filter }],
details: [postKeys.prefix, "detail"],
detail: (id) => [...postKeys.details, id]
}
Of course, I'm talking about "at scale" here. None of this is really needed for a todo app :)

How to re-fetch single items of a collection in react-query

Let's say i have a query to fetch a collection of movies:
useQuery(['movies'], getMovies)
Now, if I want to re-fetch only one movie instead of all I can write something like this:
useQuery(['movies', movieId], () => getMovie(movieId))
Problem is that I use a different query key and that it will duplicate the data. I'll have the movie in my cache twice.
So, what's the react-query way of updating a single item of a fetched collection? All components that use useQuery(['movies']) should be updated automatically when the single item was fetched.
I think there are two ways:
like you described with a new key. yes, the data will be "twice" in the cache, but oftentimes, the "list" data has a different structure than the "detail" data. movies-list might just have id and name, while the details have a description etc. as well. If you do that, make sure to give them both the same prefix ('movies'), so that you can utilize the fuzzy matching when invalidating: queryClient.invalidateQueries(['movies']) will then invalidate the list and all details, which is nice.
you can use the select option to build a detail query on:
const useMovies = (select) => useQuery(['movies'], getMovies, { select })
const useMovie = (id) => useMovies(movies => movies.find(movie => movie.id === id))
with that, you can do useMovie(5) and thus will only subscribe to the movie with id 5. This is really nice and also render optimized - if the movie with id 4 updates, the component that subscribes to movie with id 5 will not re-render.
the "drawback" is of course that for the data / network perspective, there is only one query - the list query, so everytime you do background refetches, the whole list will be queried from the backend. But it's still a nice approach to avoid data duplication in the cache, and it makes optimistic updates easier as well, because you only have to write to update one cache entry.

Apollo 3: deleting nested item from cache

I have an object type entity that can be related to another object type something through a list entityInstances. The something is also a in a list within somethingContainer:
query somethingContainer {
id
somethings {
id
entityInstances {
id
entity {
id
}
}
}
}
The entitys can appear in several entityInstances, but the instances are unique to their somethings, and so are the somethings to their containers.
The issue comes when deleting an entity. Then each of the queries that contain the entity (nested within the 3 layers) need to be updated, but it's not obvious which queries that would be.
I've looked into using cache.modify but I can't see how to use it without knowing the id's of the relevant entityInstances (or those of the relevant somethings).
Before I've used a little soft delete hack where I use writeFragment to set the id of the relevant entity to null, and filtering out these in components. But this never felt like a good solution, and it's even worse with the upgrade to Apollo 3 (we log an error whenever a query or mutation has no id, which after the upgrade also logs at writeFragment).
Another option is to do something like rerunning all somethingContainer queries but that can get expensive.
Any ideas?

React/Apollo - Similar Queries in different Component?

I am looking for help regarding best practice using Apollo, React and Meteor.
I linked the Meteor.user() model to a Schema in Apollo and I can now access it thanks to a Query Component. I have a query that looks like this :
gql`
query User {
user {
_id,
email
}
}
`
and it does the job, with a resolver giving directly the email adress. However I need it in different places and for every component where I need it I am making another < Query > component with the same Query I copy paste from on file to another. It seems to me that I am loosing the all point if many of my components are querying again and again the same things. However I do not manage to find the solution to this "DRY" problem. There are not yet so many examples including the Query component from react apollo so if someone could help me with this it'd be much appreciated.
By default, the Apollo client uses a fetchPolicy of cache-first. That means if the result of the query is already in the cache, it will be fetched from there and no network request will be made. That allows you to use the same query across multiple Query components without having to worry about making the same request to your server over and over again.
You can specify the fetchPolicy for a particular Query component if you want to override this default behavior -- for example, maybe you want to always fetch new data from the server, in which case you would use network-only or maybe cache-and-network. See the docs for more details.
NOTE: A common "gotcha" is that the cache uses the id (or _id) field to normalize the cached results. That means your queries have to include the id field (or provide a custom implementation of dataIdFromObject) to see the expected behavior. See this page for additional details.
In terms of keeping things dry, it's common practice to store your queries in one or more separate modules and then import them as needed. So you could have a queries.js file like this:
import gql from 'graphql-tag'
export const USER_QUERY = gql`
query User {
user {
_id,
email
}
}
`
graphql-tag comes with a loader that lets you import queries directly from .graphql/.gql files if you're using Webpack. Check out the recipe here. There's also a babel plugin for doing effectively the same thing (checkout it out here). Any of these approaches should reduce the redundancy in your code.
EDIT: As pointed out in #camba1's answer, fragments can also be used to DRY up your queries:
query User {
user {
...userFields
}
}
fragment userFields on User {
_id,
email,
}
Another thing that may be useful to avoid having to copy paste query code all over the place is to use query fragments .
for example:
# Query that contains a fragment
query myQuery1($_key: ID!) {
myQuery1(_key: $_key) {
field1,
...myFragmentFields
}
}
# Fragment to be used in queries
fragment myFragmentFields on myQueryType {
_key,
name,
formula,
type
}
Here is the documentation:
You can use cache-only or cache-first policy in query.
Docs

Resources