Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update store after mutation (create) unclear how to handle #1697

Closed
lucfranken opened this issue May 12, 2017 · 30 comments

Comments

@lucfranken
Copy link

commented May 12, 2017

There are a lot of docs but one part seems to be totally unclear on how to approach.

I use GraphCool as backend and React Native as frontend so that's how the names of the queries are setup.

Say I have a list of invoices

  • Invoice 1 Paid: true
  • Invoice 2 Paid: false

I have 2 queries (I show 2 lists on the screen): All & unpaid

query allInvoices {
    allInvoices {
      id
      paid
    }
}
query allInvoices {
    allInvoices(filter: {paid: false }) {
      id
      paid
    }
}

I insert a new invoice:

mutation createItemType(
    $name: String!,
    $paid: Boolean,
  ) {
    createInvoice(
      name: $name,
      paid: $paid,
    ) {
       id
       paid
    }
  }

I do understand that Apollo is not magic in a sense that it would automatically update my 2 views: all & unpaid. The advice in the documentation at this moment seems to be that I should use: update()

So I do that:

@graphql(gql`
  mutation createInvoice(
    $name: String!,
    $paid: Boolean,
  ) {
    createInvoice(
      name: $name,
      paid: $paid,
    ) {
      id,
      paid,
    }
  }
`, {
  name: 'createInvoice',
  options: {
    update: (proxy, { data: { createInvoice } }) => {
      const allInvoicesQuery = gql`
        query allInvoicesQuery {
          allInvoices {
            id
            paid
          }
        }
      `;

      const data = proxy.readQuery({ query: allInvoicesQuery });
      data.allInvoices.unshift(createInvoice);
      proxy.writeQuery({
        query: allInvoicesQuery,
        data
      });
    },
  },
})

The problem: The All invoices list will get updated. The one with the paid filter does not.

It's totally unclear how to get this right. I even tried adding variables to the writeQuery and readQuery but it just doesn't seem to work out. The simple examples in the tutorials DO work but it seems the more complex cases don't.

Questions:

  1. Where to find correct examples about real-life situations where you do filter data?
  2. Is all of this code really needed? It's a bug amount of code (I have many more fields) for just a create statement?
@saudpunjwani101

This comment has been minimized.

Copy link

commented May 14, 2017

in my case the update function doesn't gets called.

@lucfranken

This comment has been minimized.

Copy link
Author

commented May 18, 2017

Got some interesting feedback on Slack which I think should be added:
https://apollographql.slack.com/archives/C0Z9ZBDF0/p1495109398114586

As far as I understand for now I would have to do this part for EVERY query?

    const data = proxy.readQuery({ query: allInvoicesQuery });
      data.allInvoices.unshift(createInvoice);
      proxy.writeQuery({
        query: allInvoicesQuery,
        data
      });

So every time I do a query on the same nodes I would have to change all update calls? So all code becomes strongly dependent on each other? That makes all code hardcoded connected?

For example: In my "View account" component I cannot see my total amount of unpaid invoices unless I hardcode a writeQuery in my invoices list to update that component?

@jaydenseric

This comment has been minimized.

Copy link
Contributor

commented May 19, 2017

Not a "proper" solution, but I have given up on attempting smart cache busting after running mutations. I just decorate the component with withApollo and in the mutation success callback run this.props.client.resetStore(). Mutations don't happen that often, so it's not too bad that users have to refetch everything; in my app it adds less than a second.

@lucfranken

This comment has been minimized.

Copy link
Author

commented May 19, 2017

While totally agreeing that this should not be needed it sounds like a very simple and effective fix for the time being!

Another alternative is just polling:

http://dev.apollodata.com/react/api-queries.html#graphql-config-options-pollInterval

export default graphql(gql`query { ... }`, {
  options: { pollInterval: 5000 },
})(MyComponent);

Even easier because you can implement it without any callback.

@jurajkrivda

This comment has been minimized.

Copy link

commented May 23, 2017

You can get all the invoices and then filter those that are paid. Then you can use the mutation recommended above with readQuery and writeQuery helpers.

@lucfranken

This comment has been minimized.

Copy link
Author

commented Jun 2, 2017

Thanks for the feedback @jurajkrivda !

I think if I understand you well:

  const data = proxy.readQuery({ query: allInvoicesQuery });

  data.allInvoices.push(createInvoice);
  proxy.writeQuery({
    query: allInvoicesQuery,
    data
  });

  // Then manually filter that array by paid = true and write it to 
  // allInvoices(filter: {paid: true })

  // Then manually filter that array by paid = false and write it to 
  // allInvoices(filter: {paid: false })

That's the logic? While I would hope that it seems duplicated logic (I already wrote that query) and very cumbersome if you get additional parameters as it will become a matrix?

Let's say I have 3 boolean parameters that's already 2 * 2 * 2 = 8 manual filters I have to implement to update the data? If I have more it becomes a huge amount?

@stale

This comment has been minimized.

Copy link

commented Jul 15, 2017

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

@lucfranken

This comment has been minimized.

Copy link
Author

commented Jul 17, 2017

@probot-stale[bot] actually this issue is still open and wondering whether there are any new strategies which have been developed recently.

@ctavan

This comment has been minimized.

Copy link

commented Jul 17, 2017

@lucfranken first of all I believe that this is a duplicate of (or at least related with) apollographql/react-apollo#708

In fact I ran into exactly the same issue and ended up calling all queries in the update hook with all parameters as you describe. This is particularly bad since you don't really know which of all those queries are already in the store and actually need an update. So after an update you end up with a store that is much large than what would often be necessary.

I was hoping that I could at least simplify my case by using refetchQueries which would be a little less radical than resetting the entire store as suggested by @jaydenseric. When using refetchQueries with just the query name it should actually refresh all queries with a given name in the store, irrespective of the variables/parametrizations. Unfortunately it currently doesn't work as expected due to a race-condition which I have reported in: #1821

So for now I'm stuck with the repeated store updates which works but really adds a lot of logic and code while I would find a simple refetch of all queries that are already in the store to be a good compromise between maintainability and client-side performance.

Edit: Oh, and as you mention in your last comment @lucfranken, it also only works for me because I currently have queries with one boolean parameter. As you mention this would soon become impossible for more complex parametrizations (think: pagination where you don't actually know the total number of pages)…

@lucfranken

This comment has been minimized.

Copy link
Author

commented Jul 24, 2017

@ctavan yes party it's related but this issue actually was created based on the recommendations in the documentation that the refetchQueries would be deprecated. Input from the Apollo team on the right direction is needed.

@stale

This comment has been minimized.

Copy link

commented Aug 14, 2017

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

@pawsong

This comment has been minimized.

Copy link

commented Aug 19, 2017

It would be helpful if there is an API like readQueries or readQueryWithVariables which returns all cached parameters of a parameterized query. Then I can manually update each query caches. My 2 cents :)

@stale stale bot removed the no-recent-activity label Aug 19, 2017

@stale stale bot added the no-recent-activity label Sep 9, 2017

@stale

This comment has been minimized.

Copy link

commented Sep 9, 2017

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

@stale

This comment has been minimized.

Copy link

commented Sep 23, 2017

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

@stale stale bot closed this Sep 23, 2017

@aunnnn

This comment has been minimized.

Copy link

commented Oct 5, 2017

Hi, is there any updates on this issue?

So now I have to either:

  1. use 'refetchQueries' or,
  2. use 'update' to update the store for all related queries,

Is that right?

@lucfranken

This comment has been minimized.

Copy link
Author

commented Oct 5, 2017

@aunnnn this issue got closed automatically. Asked about that here: #1910

As I wrote months ago:

Input from the Apollo team on the right direction is needed.

No idea why there is no answer.

@adavia

This comment has been minimized.

Copy link

commented Jan 3, 2018

I had experience a situation like this where i needed to update an item from a filtered list and i handled it using the componentWillReceiveProps lifecycle method.

Example

componentWillReceiveProps(newProps) {
    const { allInvoices, loading } = newProps.data;

    if (!loading) {
      const invoices = _.intersectionBy(allInvoices, this.state.invoices, 'id');
      this.setState({ invoices });
    }
  }
@Vitiell0

This comment has been minimized.

Copy link

commented Jan 25, 2018

If anyone is still running into this problem, we have found a solution. Just pass the variables with submit and they will be available in update.

screen shot 2018-01-25 at 12 20 09 pm

@minardimedia

This comment has been minimized.

Copy link

commented Apr 12, 2018

Why the Apollo team is ignoring this. It is a to common problem and they are no doing anything about it. Their may be maybe a way but there is nothing document about it really dissapointing

@lucfranken

This comment has been minimized.

Copy link
Author

commented Apr 17, 2018

It seems that refetchQueries is not deprecated anymore. All docs are shuffled around so it's not easy to find back the early mentions of this but it seems changed in the docs.

@DarKDinDoN

This comment has been minimized.

Copy link

commented Apr 20, 2018

How am I supposed to know which argument I should use with refetchQueries ? refetchQueries doesn't seem to be a solution either ...

@renato

This comment has been minimized.

Copy link

commented Jun 4, 2018

@DarKDinDoN I'd prefer a better solution using update, but refetchQueries also works with strings, like this:

<Mutation mutation={CREATE_FOO} refetchQueries={["allFoos"]}>

From https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-mutation-options-refetchQueries:

If options.refetchQueries is an array of strings then Apollo Client will look for any queries with the same names as the provided strings and will refetch those queries with their current variables.

@DarKDinDoN

This comment has been minimized.

Copy link

commented Jun 4, 2018

@renato yes but am I supposed to know which argument I should provide to the query ? Especially, when the parameters are dynamic.

@renato

This comment has been minimized.

Copy link

commented Jun 4, 2018

No, using strings those queries will be refetched using their current variables - which may solve some situations.

I'll probably use it whenever possible or do some kind of cache invalidation (ideas on #621 #899) otherwise.

@minardimedia

This comment has been minimized.

Copy link

commented Jun 8, 2018

@DarKDinDoN I try it all I get your point their is no way you can know all the multiple dynamic queries that are being generated by the users. I still don't know why if is such a common thing they don't provide a way to refetch ALL.

Anyway if works for something my solution was to Flush the whole cache, I know it's not a nice solution but works for me since I don't expect user will be adding and removing to much elements anyway. Also cache refresh more often that we think like for example if users recharge the page so it is no that big o a deal.

Here is how can you implement this.

`
import { withApollo } from 'react-apollo';

export default withApollo(YOURCOMPONENT);

//this will give you access to the client as a prop so inside your component, after the add or delete action you just call

this.props.client.resetStore();
`

@acomito

This comment has been minimized.

Copy link

commented Jun 17, 2018

@minardimedia one issue I have with resetStore is it includes reseting my auth/user queries, which causes mayhem in apps that have an "authenticated" area (where user would get kicked back to login or content would flicker).

@minardimedia

This comment has been minimized.

Copy link

commented Jun 18, 2018

Hi @acomito I just came to know about this repo apparently it works for this issue, I haven't try it but you can take a look at it (apollo-link-watched-mutation) https://github.com/haytko/apollo-link-watched-mutation

@yashwp

This comment has been minimized.

Copy link

commented Jul 10, 2018

I'm working with this in apollo-angular -
screenshot 14
I hope that helped :)

@DarKDinDoN

This comment has been minimized.

Copy link

commented Jul 10, 2018

@yashwp thanks, your example covers a very simple example. Try it when ALL_COURSES has many filters/parameters.
Y'll that it is not scalable to cover every QUERY and their parameters by hand.

@vitaliy-ostapchuk93

This comment has been minimized.

Copy link

commented Jun 3, 2019

@yashwp Thanks! Tried it out on my own models and it works! very simple and nice code :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
You can’t perform that action at this time.