Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

Updating cached data from multiple parameterized queries after a mutation #708

Closed
c10b10 opened this issue May 12, 2017 · 24 comments
Closed
Labels

Comments

@c10b10
Copy link

c10b10 commented May 12, 2017

Suppose I have a paginated infinite scroll list of Items. The list pulls data from a GraphQL server by using two parameterized queries: ITEMLIST_QUERY and ITEMLIST_MORE_QUERY. Both of those queries have arguments for ordering, and filtering, and whatever else is needed for pagination. The only difference between them is that the MORE query has an extra $skip argument (I know I could have used a single query with a default $skip set to 0, but the question stands, since I could have the same query called with different variables).

Now suppose a user loads a few pages of data, ie.

  • ITEMLIST_QUERY gets called at component mount, data is loaded into the store, first 10 Items are rendered on the page.
  • User scrolls, ITEMLIST_MORE_QUERY gets called with a $skip: 10 arg, data is loaded into the store, the 10 new items are appened in the UI.
  • User scrolls again, ITEMLIST_MORE_QUERY gets called again with a different $skip, data is loaded into the store, the UI now contains 30 `Items.

Now suppose the user clicks Delete for a certain item in the list, and the ITEM_DELETE_MUTATION mutation gets called.

How does one update the data for the appropriate query in the store (with the mutated data) by using the recommended update in ordered to update the UI to reflect the deletion?

proxy.readQuery seems to not be able to help, since I can't be sure of the number of queries executed, or of which query contains the deleted Item.

Is there a way to get all data from all queries that match a certain pattern? For example:

proxy.readQuery({ queriesMatch: 'allItems' })

Is there a way of doing this by using update? The only way I can think of is to manually save the queries that I want to reset (with their variables) in the store, and then reset them after each mutation.

@lucasfeliciano
Copy link

By default mutations updates happens based on node's id that are returned from the dataIdFromObject that you defined and it will update with the results of your mutations.

If that is not enough, you might need define the options.update function.

You can find more information in the docs her: http://dev.apollodata.com/core/read-and-write.html#updating-the-cache-after-a-mutation

@c10b10
Copy link
Author

c10b10 commented May 17, 2017

dataIdFromObject would be useful if I could somehow get the data and then update it by using this id. Something like:

const data = client.getDataFromStore(client.dataIdFromObject(mutationResult))

and then for different kind of mutations I would need a way to update the data in the store: delete the node from the store in the case of a delete mutation, change it for an update mutation and so on. Is this possible with the current API?

If not, any idea of how to update the cache using options.update when I know the query that I want to update, but I don't know the variables that it has supplied with?

As an example of a use case, in a pagination scenario, I could have dozens of calls to a certain query (that loads each successive page), each of those calls having different variables (relating to the loaded page). Suppose I then mutate an item from the results of one of those queries. How would I go about updating the correct query results in store?

I thought about using readQuery, but it requires the query variables as an argument in order to get access to the data. The problem is that I can't really know which of the multitude of calls to the pagination query contains the mutated result. It may be multiple calls, with different variables (I may have it contained in filtered results, and normal results).

How do I update all queries that contain the Item that has been mutated?

@lucasfeliciano
Copy link

lucasfeliciano commented May 19, 2017

You don't need to update all queries, you just need to update the item in the store which was mutated and apollo will update all references to related to that item.

Example:

// We assume that the GraphQL operations `DeleteToDo` and
// `TodoAppQuery` have already been defined using the `gql` tag.
const id = 42
client.mutate({
  mutation: DeleteToDo,
  variables: {
    id,
  },
  update: (proxy, { data: { deletedToDoId } }) => {
    // Read the data from our cache for this query.
    const data = proxy.readQuery({ query: TodoAppQuery })
    // Remove the deleted todo
    data.todos.filter(todo => todo.id !== deletedToDOId)
    // Write our data back to the cache.
    proxy.writeQuery({ query: TodoAppQuery, data })
  },
})

If your server doesn't return the id of the deleted item you still have the id that you passed into the mutation variable.

Ps: If I didn't get your issue right can you comment some code snippets so I can reproduce what you are trying to achieve?

@stale stale bot added the wontfix label Jul 5, 2017
@stale
Copy link

stale bot commented Jul 5, 2017

This issue has been automatically marked as stale becuase it has not had recent activity. It will be closed if not further activity occurs. Thank you for your contributions to React Apollo!

@ctavan
Copy link

ctavan commented Jul 6, 2017

Potentially apollographql/apollo-client#1821 is related as well?

@stale stale bot removed the wontfix label Jul 6, 2017
@jonmanzo
Copy link

jonmanzo commented Jul 7, 2017

You can define the key name via a connection directive now...

apollographql/apollo-client#1801

@CrocoDillon
Copy link

I'm running into a similar problem. When a query accepts one or more variables I want to either refetch or update that query with all possible variables it has been cached with.

Use-case is something like adding to or removing items from the top of some infinite scroll list or paginated results. In my case it's a query that retrieves just the right amount of items that fit on the screen.

const query = gql`
  query ItemQuery($first: Int) {
    items: allItems (first: $first, orderBy: createdAt_DESC) {
      id
    }
  }
`;

This query gets executed a few times with results as follow:

// $first === 3 (default)
items === [7, 6, 5]
// $first === 6 (after react-measure adjustments)
items === [7, 6, 5, 4, 3, 2]

If I add a new item with id === 8 I want to update both (could be more though) queries to:

// $first === 3
items === [8, 7, 6]
// $first === 6
items === [8, 7, 6, 5, 4, 3]

How do I do that?

@stale
Copy link

stale bot commented Aug 25, 2017

This issue has been automatically labled because it has not had recent activity. If you have not received a response from anyone, please mention the repository maintainer (most likely @jbaxleyiii). It will be closed if no further activity occurs. Thank you for your contributions to React Apollo!

@stale stale bot closed this as completed Sep 8, 2017
@stale
Copy link

stale bot commented Sep 8, 2017

This issue has been automatically closed because it has not had recent activity after being marked as no recent activyt. If you belive this issue is still a problem or should be reopened, please reopen it! Thank you for your contributions to React Apollo!

@vedmakk
Copy link

vedmakk commented Jan 31, 2018

Did anybody find a proper solution here? I think this is a use-case a lot of people will run into and the feedback provided by @lucasfeliciano doesn't cover that. Let me try with another Example:

You have a list of "Liked Objects". But this list is filterable by color. So your query would look like this:

getLikedObjects(userId: $id, colors: $color) ...

The user calls the query 3 times with colors: null, colors: [blue] and colors: [yellow, blue]

Now the cache will have 3 entries for that query (based on different variables).

Entry 1 (colors: null): [Object 1, Object 2, Object 3]
Entry 2 (colors: [blue]): [Object 2]
Entry 3 (colors: [yellow, blue]: [Object 2, Object 3]

Now imagine the user unlikes "Object 3". This would mean we can't just wipe out the Object in the Cache using dataIdFromObject with readFragment --> We wan't to keep the Object itself in the Cache but only remove it from the "getLikedObjects"-Queries that contain it (Entry 1 and Entry 3).

But we don't know how many times the user has filtered with what values... So what we would like to do is:

Remove Object 3 from all getLikedObjects-Queries that contain it – regardless of any variables provided to the query

So a more flexible way to match Queries in "readQuery" as suggested by @c10b10 (for example using Regex) would work…!?

@cuxs
Copy link

cuxs commented Feb 2, 2018

@protyze In here
apollographql/apollo-client#1697 (comment)
But this does not work for me because of what you have explained.
It would be nice if "readQuery" returns an array of matches for example, instead of an error.

@minardimedia
Copy link

Any update on how to solve this problem

@nfantone
Copy link

nfantone commented Jul 25, 2018

As it is already normalized, I feel like there should be an API to read data from the cache as if it were a classic Map implementation: fetch node by key, set node to a given key, get all keys, get all nodes, get all entries.

This reading of values using queries (or fragments) as inputs looks awkward and forces overlapping of responsibilities by having to pass around things, like query strings or user inputs that determine variables, that might not concern the actual component executing the mutation.

It's unfortunate to see this issue closed, by the way.

@newms87
Copy link

newms87 commented Aug 21, 2018

@nfantone I agree,

There should be a way to bust the cache for a regex / loose match like what redis has would be amazing! I hate having to pass around the original Query or recreate the original query just to get at some object data. I know I can hack things and access the cache directly and delete things but it is a big work around.

The current state of things makes pagination cache busting impractical. Its better to just clear the whole cache. This API really needs to get updated please!

@sandorvasas
Copy link

This is an issue for me too. I have a query (getItems) that uses fetchMore in an infinitely scrolling list, with variables { user_id, skip, limit }. When I run the updateItem mutation, I don't know what (skip,limit) pair the item falls between, therefore I just want to (at least) access all queries named getItems in the cache by userId, for all skip/limit pairs.

readQuery({ query: getItems, variables: { user_id: 10 }) should read all queries where user_id is 10 instead of requiring to set all variables.

@Grmiade
Copy link

Grmiade commented Sep 6, 2018

@sandorvasas For this kind of issues, you can use the connection directive ;)
https://www.apollographql.com/docs/react/features/pagination.html#connection-directive

@ffxsam
Copy link

ffxsam commented Feb 22, 2019

This is a massive shortcoming in Apollo in general (whether using it with React, Vue, plain JS). Results from queries using variables like sort/sortBy are all cached separately. There needs to be a built-in utility to handle this.

@dcworldwide
Copy link

Still no solution?

@ngryman
Copy link

ngryman commented Jun 29, 2019

I would also love to see such a feature part of the cache API.

Meanwhile, for those who are interested, I've created a function to get all variants of a query in the cache. It accepts an instance of the proxy and gql query object. It returns a list of all the variants of variables for that query, so you can then use proxy.readQuery and proxy.writeQuery for each of those variants.

Here is the gist (only tested with InMemoryCache).

Example

update: (proxy: any, { data: { addTask } }: any) => {
  const query = QUERY_TASKS
  const variablesList = getVariablesListFromCache(proxy, query)

  for (const variables of variablesList) {
    const data = proxy.readQuery({ query, variables })
    data.tasks.push(addTask)
    proxy.writeQuery({ query, data })
  }
}

@uladkasach
Copy link

uladkasach commented Aug 13, 2019

To anyone with this same problem today: the @connection directive solves this problem specifically https://www.apollographql.com/docs/react/advanced/caching/#the-connection-directive

here is an example of its usage: https://stackoverflow.com/a/48262766/3068233

@ffxsam
Copy link

ffxsam commented Aug 13, 2019

It's been months since I was working with this, but I remember @connection not working as expected.

@eric-burel
Copy link

eric-burel commented Sep 26, 2019

@ngryman made my day. It works perfectly with Apollo-client 2.6.3 in a case where apollo-link-watched-mutation does not seem to work anymore. It also works after server side rendering, when the queries haven't been run yet.

However I need to match on the resolver name, not on the query, see my comment on the gist.

I've had it to work with a delete mutation. Creation is more complex because some queries might have filtering but it's doable.

@tafelito
Copy link

tafelito commented Jan 27, 2020

@connection directive is meant for cases where you have an infinite list. Is not meant for numbered pages pagination (unless you do all the split yourself for every page/rowsPerPage) . Even with @ngryman solution, it won't work for cases where you are inserting a new item, because you don't know where you have to put it in different queries with different variables, for example if you have a sorted list.
A simple way to solve this would be to invalidate the cache for those queries and refetch, or the worst case would be refetch all the queries with the same name.
Today apollo has that functionality built-in with refetchQueries but it just doesn't work apollographql/apollo-client#5419

I guess this is not a big deal since nobody for the apollo team had addressed this issue. 🤷‍♂️  

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests