React-apollo: [Feat] refetchQueries with "reuse variables" option

Created on 5 Jul 2017  路  35Comments  路  Source: apollographql/react-apollo

Lets say we have the following:

type Item {
  id: ID!
}
type Query {
  items(filter: ItemListFilter): [Item]!
}
type Mutation {
  addItem(input: AddItemInput!): Item
}

This query in use in a mounted component A, filter variable coming from props of A via graphql HOC:

query ItemList($filter: ItemListFilter) {
  items(filter: $filter) {
    id
  }
}

And I execute the following mutation in a mounted component B (graphql HOC):

mutation AddItem($input: AddItemInput!) {
  addItem(input: $input) {
    id
  }
}

I want to update the query in component A on mutation in component B. But when I do so using refetchQueries and I do not provide the same variables as currently passed to component A, the wrong query is updated. It is correct, since the query cache key is distinct by variables and name, not just the name. You have as many cache keys as combination of variable inputs and name ever provided. Its all correct, I understand.

What would be convenient is the possibility to achieve similar effect like a call to refetch provided by the data property in component A, just from the component B. That means, that the the query is executed with current props from component A, so when a filter is set via A's props, the correct query is updated, without a reference to A inside B - just by query name.

Is it possible to resolve which components use specific query by NAME and then refetch those queries with current props on the component? If it is possible, a boolean option for refetchQueries for example "reuseProps" as explicit distinction to current behavior when no variables are passed could be very convenient.

Most helpful comment

I believe that this feature is important. Why recalculate the variables when you can just reuse the one that you already have in the cache?

All 35 comments

So I tried an approach creating a selector like that:
(Sadly I could not find selectors neither in docs (both react-apollo and apollo-client), nor source code.

import {createSelector} from 'reselect';
export const getApolloQueries = document => createSelector(
  root.getApolloState,
  state => _.map(
    _.filter(state.queries, query => query.document === document),
    query => ({query: query.document, variables: query.variables}),
  ),
);

This did not work, because it seems the compiled document you pass to HOC does not have the same identity as the one in store. First I thought its due to my session meta reducer, which JSON.stringify/parse data, but this is also the case when I disable it and clear all cache/storage.

So I ended up with this solution:

export const getApolloQueries = document => createSelector(
  root.getApolloState,
  state => _.map(
    _.filter(state.queries, query =>
      query.document.definitions[0].name.value === document.definitions[0].name.value
    ),
    query => ({query: query.document, variables: query.variables}),
  ),
);

This works, but does not look like a very good solution in my opinion, because it does make assumptions I'm not sure is a good thing to do.

Note. I'm totally aware of the possibility to manually update queries using code. But I prefer the way of updating it via data coming from API automatically. I think it is always better than writing code to sync stuff. It is less prone. I always try to add as few complexity in a project as possible and updating from mutation result automatically is less code and easier to reason about.

This is why I'm following the refetchQueries approach and I think this feature is important.

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!

still relevant
@jbaxleyiii

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!

@jbaxleyiii

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!

@jbaxleyiii

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!

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!

I believe that this feature is important. Why recalculate the variables when you can just reuse the one that you already have in the cache?

I gave it up bumping here.

My solution was to switch to relay modern. I did complete a project with apollo, but with many workarounds and hacks and still existing quirks. Most troubles did not only __not__ occur with relay modern, but even worked better out of the box - even though not as good documented as apollo.

I understand that this project does not have much man-power, so I can't blame anyone and the persons behind the project seems to be nice, which make the whole thing a bit sad ;(

what is the status of re-fetching while re-using vars from previous call

You might check if it made into apollo in V2, not sure what the progress there is. As stated, I now use relay modern.

I think we should reopen this thread, because this feature is very important and I'm pretty sure it is easy to implement.

@hwillson, @excitement-engineer, @rosskevin, @danilobuerger any thoughts on this?

Is there an actual real world use case to this?

@danilobuerger My use-case is the following. I have a list of items at homepage. At /add page after submitting a new item I redirect the user to homepage and refetch the query so that he can see the new item in the list. But if I use variables for the homepage query, then refetchQueries at /add page doesn't work if I don't provide the same variables.

so a refetch with the last used variables for that named query?

@danilobuerger yes, exactly.

I would think this case is covered already in the Todo app. In this case, post-mutation update should be using writeQuery to update the cache, then the home page will be automatically updated.

Note the open 2415 bug in apollo-client. I'm mobile and don't have a link.

@rosskevin sure it can be done with update, but we have two ways to update for a reason:

  • refetchQueries for a coarse update strategy
  • update for a fine update strategy

Looking at the source, it can easily be done with refetchQueries:

  • add a reuseVariables: boolean field to the RefetchQueryDescription object

Before https://github.com/apollographql/apollo-client/blob/master/packages/apollo-client/src/core/QueryManager.ts#L226 do :

// Pseudo code

if (refetchQuery.reuseVariables) {
    const refetchedQueries = this.queryIdsByName[refetchQuery.query];
    if (refetchedQueries === undefined) return;
    refetchQueryPromises.push(Promise.all(
      refetchedQueries
        .map(id => this.getQuery(id).observableQuery)
        .filter(x => !!x)
        .map((x: ObservableQuery<any>) => x.refetch(x.variables)),
    ));
    continue;
}

I'm not against it if useful, but I am also not sure @yantakus knows this case is covered by the update + writeQuery.

Ah yes, you are right. I just assumed that using update was not wanted. In any case, I will prepare a PR in the apollo-client repo

@rosskevin I know this technique but refetchQueries is much more straightforward one. I always try to make code as simple as possible, so that it is readable and maintainable. Why using a lot of custom code (which is always error-prone) instead of using a single line?

@yantakus in the case of update + writeQuery, we avoid another server hit altogether. So the answer is performance and efficiency (both client and server). If that's not a concern or there are other factors at play then use refetchQueries.

Actually never mind my code, it should already work if refetchQueries is an array of strings.

When refetchQueries is an array of strings, it will call refetch on each ObservableQuery and refetch reuses the old variables if no new ones are set (which is the case here).

So then I don't understand whats not working here @yantakus

@danilobuerger actually I never tried it with array of strings. I'm using an array of objects like this: refetchQueries={[{ query: VIDEOS_QUERY }]}. In this case query names are stored in variables and are reused across the app. In case of array of string it isn't possible as I understand.

So why cant you use the actual name of the query? Assuming VIDEOS_QUERY is query foo { ... } you could refetchQueries={['foo']}

I can do this, but it's not a complete replacement because using variables is more reliable. Using strings is fragile. For example query name is changed, refetchQueries stops working. In case of using variables it can't happen.

Nobody is stopping you to use variables:

refetchQueries={[getOperationName(VIDEOS_QUERY)]}

@danilobuerger Thanks, I didn't know about this util. BTW, I didn't manage to find anything about it in the docs.

Please note that if you call refetchQueries with an array of strings, then Apollo Client will look for any previously called queries that have the same names as the provided strings. It will then refetch those queries with their current variables.

https://www.apollographql.com/docs/react/advanced/caching.html

Yes, I see. I mean there's nothing about getOperationName there. And anywhere else. Would be reasonable to add.

Best way to fix that is if you open a PR and add the documentation

@danilobuerger Hi! I was facing a similar issue, with the difference that even if i refetch a query where it were setted new variables (after performing a fetchMore operation - changing for example the offset variable), the refetch query calls the query with the default parameters (where offset is always 0). Is this the correct behavior? Thanks!

Was this page helpful?
0 / 5 - 0 ratings