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

Components never get updated after writeData or writeQuery #3909

Closed
wzup opened this issue Sep 12, 2018 · 66 comments
Closed

Components never get updated after writeData or writeQuery #3909

wzup opened this issue Sep 12, 2018 · 66 comments

Comments

@wzup
Copy link

wzup commented Sep 12, 2018

Intended outcome:
Components update automatically after cache modification with writeData or writeQuery.
You repeat it everywhere in your docs:

Any subscriber to the Apollo Client store will instantly see this update and render new UI accordingly.

Actual outcome:
Components never get updated after writeData or writeQuery.

You have to call broadcastQueries() like so:

this.props.client.queryManager.broadcastQueries();

in order to force your <Query> tags to reload themselves to render your new cache data.
But after you call broadcastQueries() a new issue arise - it reloads ALL your <Query> tags, not just those that you need. Huge performance issue.

ANd you have nothing in your docs about it. Question here

How to reproduce the issue:

class FooComponent extends PureComponent {
  
  componentDidMount() {
    
    const query = gql`
      query QQQ {
        Me {
          __typename
          id
          something
        }
      }
    `;
    
    // I call writeQuery manually
    // How does <Text> input change instantly with new value?  
    // Or change it to writeData
    this.props.client.cache.writeQuery({
      query: query,
      data: {
        Me: {
          __typename: 'User',
          id: 'be2ae9d718c9',
          something: 'ABSDEF'
        }
      }
    });
  }
  
  render() {
    
    let something = this.props.Something.something;
    
    // How does <Text> input change instantly with new value 
    // after I call writeQuery in componentDidMount above?
    return (
      <View>
        {/* HOW DOES IT CHANGE instantly after writeQuery or writeData ?? */}
<Query query={ GET_SOMETHING }>
      { params => {
        const data = get(params, 'data.Me.something', {}) || {};
        const something = data.something;
        return (
          <Text>
            {something}
          </Text>
        )
      } }
    </Query>
      </View>
    )
  }
}

export default compose(
  withApollo,
  graphql(getSomethingQuery, {
    name: 'Something'
  }),
)(FooComponent);

Versions

  System:
    OS: macOS High Sierra 10.13.4
  Binaries:
    Node: 8.11.3 - /usr/local/bin/node
    npm: 5.6.0 - /usr/local/bin/npm
  Browsers:
    Chrome: 68.0.3440.106
    Safari: 11.1
  npmPackages:
    apollo-boost: ^0.1.4 => 0.1.10 
    apollo-cache-inmemory: ^1.1.0 => 1.2.5 
    apollo-client: ^2.0.3 => 2.3.5 
    apollo-link: ^1.0.3 => 1.2.2 
    apollo-link-http: ^1.2.0 => 1.5.4 
    react-apollo: ^2.1.1 => 2.1.9 
@reinvanimschoot
Copy link

any update on this? Just came across the same problem and it's rather frustrating

@good-idea
Copy link

@wzup, This was happening to me and I (think) I fixed it by using client.writeQuery instead of client.cache.writeQuery. I'm not sure what the difference between these two functions is - but when I use the former, the rest of the components are updated with the new, written results.

@reinvanimschoot
Copy link

@good-idea Is this something you can do when using the graphql hoc?

@good-idea
Copy link

@reinvanimschoot I believe so - this is what it looks like in the docs:

const query = gql`{ todos { ... } }`

export default graphql(gql`
  mutation ($text: String!) {
    createTodo(text: $text) { ... }
  }
`, {
  options: {
    update: (proxy, { data: { createTodo } }) => {
      const data = proxy.readQuery({ query });
      data.todos.push(createTodo);
      proxy.writeQuery({ query, data });
    },
  },
})(MyComponent);

I'm not sure if proxy is any different from the client available through the render prop components - but it looks like proxy.writeQuery works the same way.

@rohindaswani
Copy link

Did anyone come to a resolution with respect to this? I'm facing a similar problem in that when I add the updated data to the cache with writeQuery the component doesn't re-render. I'm also facing a related problem in which I need the component's parent to re-render as well since the underlying data has changed. Any thoughts on how to do that?

@bberak
Copy link

bberak commented Dec 18, 2018

Hey @rohindaswani - when you're calling writeQuery, are you attempting to update local state (as in https://www.apollographql.com/docs/react/essentials/local-state.html).

I found that all my queries that read from local state (rather than the remote server) do not get refreshed after calling writeQuery or writeFragment.

My workaround was to explicitly call a local mutation that modifies the local state instead of writing to the cache/proxy directly with writeQuery or writeFragment. Since the state/data is written locally, calling the mutation directly doesn't penalize performance or anything (imo anyway).

Something like:

export const remoteMutationHOC = Wrapped => {
  return props => (
    <ApolloConsumer>
      {client => (
        <Mutation
          mutation={remoteMutation}
          update={(cache, { data: { result } }) =>
            //-- This is where I would normally call cache.writeFragment, but since the
            //-- data I need to update lives locally, I can call a local mutation. After the call
            //-- to the local mutation - all my queries that depend on the local data will get refreshed.
            client.mutate({
              mutation: localMutation,
              variables: { result }
            })
          }
        >
          {(mutation, { loading, data }) => (
            <Wrapped {...props} mutation={mutation}  />
          )}
        </Mutation>
      )}
    </ApolloConsumer>
  );
};

@ctretyak
Copy link

ctretyak commented Dec 21, 2018

any update on this? Just came across the same problem and it's rather frustrating

you need to use another instance of the data object like this

update: (proxy, { data: { createTodo } }) => {
      const data = _.cloneDeep(proxy.readQuery({ query }));
      data.todos.push(createTodo);
      proxy.writeQuery({ query, data });
    },

@rosskevin
Copy link
Contributor

rosskevin commented Dec 24, 2018

Duplicate of #2415 ?

Interesting observation @ctretyak - though I think that should not be required. Nonetheless, good insight into why this might be happening. This is the original code that was supposed to fix the issue: https://github.com/apollographql/apollo-client/pull/2574/files

I'm hoping to track this down.

Edit: discussion about deep copy #4031 (comment)

@Mughees605
Copy link

Mughees605 commented Dec 28, 2018

I am also facing the problem its updated the cache but no updating the component

 let data = cache.readQuery({ query: wsGroups, variables: { w_id: wid } });
        console.log(data)
        data.wsGroups = data.wsGroups.map(group => {
            if (group.group_id === group_id) {
                group.members = group.members.filter(member => member.user_id !== payload.data.deleteMemberInWsGroup.user_id)
                return group;
            }
            else {
                return group;
            }
        });
         cache.writeQuery({ query: wsGroups, data: data, variables: { w_id: wid } });
           // here i can see that cache is updated
         console.log(cache.readQuery({ query: wsGroups, variables: { w_id: wid } }))

    //but component props is not changed

@kokokenada
Copy link

kokokenada commented Dec 30, 2018

Having similar problems. I observe the cache is updated, but a component that directly uses the Query does not re-render. client.queryManager.broadcastQueries(); does not help

@rohindaswani
Copy link

rohindaswani commented Jan 3, 2019

I've found a solution to this. It took a lot of digging around to find out how to get the parent to re-render. You can get the component to re-render by calling update in the Mutation props and/or refetchQueries to refetch the query and update local state.

update and refetchQueries will only update that specific component after the mutation. It will not re-render the parent or grand-parent components. If you want to reload the query (for a parent) in response to a user action, the Query component passes a method called refetch that you can call or pass down to child components when you want that component to refetch it's query.

Here is an example.

<Query
              query={GET_RECORDED_ON_DATES}
              variables={{ patientId: this.props.patientId, limit: 1 }}>
              {({ loading, error, data, refetch }) => {
                let recordedOnDates = this.getDatesAndSort(data);
                let startDate = recordedOnDates[0] || START_DATE;
                return (
                  <div>
                    <div className="MainContainer__section MainContainer__section--header">
                      <TimelineHeader
                        categories={filteredCategories}
                        startDate={startDate}
                        endDate={END_DATE}
                      />
                    </div>

                    <div>
                      {filteredCategories.map(item => {
                        return (
                          <Fragment key={`category:${item.id}`}>
                            {item.isToggled &&
                              this.getCategoryComponent(
                                item.name,
                                item.id,
                                startDate,
                                componentWidth,
                                refetch
                              )}
                          </Fragment>
                        );
                      })}
                    </div>
                  </div>
                );
              }}
            </Query>

You can read more on refetching here: https://www.apollographql.com/docs/react/essentials/queries.html#refetching

@ghost
Copy link

ghost commented Feb 12, 2019

I was having the same issue. After reading @good-idea 's comment, I replaced client.cache.writeQuery with client.writeQuery and it started to work on my HOC (withApollo component).

@cnoter
Copy link

cnoter commented Feb 27, 2019

I was facing very similar issue with angular~apollo,
(>>> I made it work with some workarounds, so I post it here to help :) )

I have two components, 'A' component has an apollo.watchQuery and the other 'B' component
has to do a 'cache update(writeQuery())' that affects the A components's watchQuery.
I did expect after B component's writeQuery call, the A component' watchQuery's subscribe logic would be called again, But actually it was not.

So I did suspect 'timing issue' or 'out of angular "zone" issue',
My workaround was wrapping readQuery, writeQuery in the same setTimeout function with zero ms(in B component) .
(Of course, It doesn't have to be in the same setTimeout or readQuery needed before writeQuery)

I think this Plan B setTimeout workaround is quiet clear.

@joeyzone
Copy link

Intended outcome:
Components update automatically after cache modification with writeData or writeQuery.
You repeat it everywhere in your docs:

Any subscriber to the Apollo Client store will instantly see this update and render new UI accordingly.

Actual outcome:
Components never get updated after writeData or writeQuery.

You have to call broadcastQueries() like so:

this.props.client.queryManager.broadcastQueries();

in order to force your <Query> tags to reload themselves to render your new cache data.
But after you call broadcastQueries() a new issue arise - it reloads ALL your <Query> tags, not just those that you need. Huge performance issue.

ANd you have nothing in your docs about it. Question here

How to reproduce the issue:

class FooComponent extends PureComponent {
  
  componentDidMount() {
    
    const query = gql`
      query QQQ {
        Me {
          __typename
          id
          something
        }
      }
    `;
    
    // I call writeQuery manually
    // How does <Text> input change instantly with new value?  
    // Or change it to writeData
    this.props.client.cache.writeQuery({
      query: query,
      data: {
        Me: {
          __typename: 'User',
          id: 'be2ae9d718c9',
          something: 'ABSDEF'
        }
      }
    });
  }
  
  render() {
    
    let something = this.props.Something.something;
    
    // How does <Text> input change instantly with new value 
    // after I call writeQuery in componentDidMount above?
    return (
      <View>
        {/* HOW DOES IT CHANGE instantly after writeQuery or writeData ?? */}
<Query query={ GET_SOMETHING }>
      { params => {
        const data = get(params, 'data.Me.something', {}) || {};
        const something = data.something;
        return (
          <Text>
            {something}
          </Text>
        )
      } }
    </Query>
      </View>
    )
  }
}

export default compose(
  withApollo,
  graphql(getSomethingQuery, {
    name: 'Something'
  }),
)(FooComponent);

Versions

  System:
    OS: macOS High Sierra 10.13.4
  Binaries:
    Node: 8.11.3 - /usr/local/bin/node
    npm: 5.6.0 - /usr/local/bin/npm
  Browsers:
    Chrome: 68.0.3440.106
    Safari: 11.1
  npmPackages:
    apollo-boost: ^0.1.4 => 0.1.10 
    apollo-cache-inmemory: ^1.1.0 => 1.2.5 
    apollo-client: ^2.0.3 => 2.3.5 
    apollo-link: ^1.0.3 => 1.2.2 
    apollo-link-http: ^1.2.0 => 1.5.4 
    react-apollo: ^2.1.1 => 2.1.9 

broadcastQueries Does not work for me

@rchasman
Copy link

I was just tearing my hair out over a regression where an existing component doesn't consistently update anymore after a mutation call, update : writeQuery.

I tested render, and it appears not to receive new props after a writeQuery from the mutation update even though the cache seems to be right (but sometimes it does work).

I played around a bit and was relieved (and confused) to see it behaving properly and consistently after some very minor changes making the data write immutable. Here's some examples:

Bad, flaky component re-rendering:

      update: (cache, { data: { createFieldMapping } }) => {
        const data = cache.readQuery({
          query: fieldMappingsGQL,
          variables: { type, integration },
        });
        data.fieldMappings.push(createFieldMapping);
        cache.writeQuery({
          query: fieldMappingsGQL,
          variables: { type, integration },
          data,
        });
      },

These very minor changes (can you spot them?) made the mutation update work flawlessly again:

      update: (cache, { data: { createFieldMapping } }) => {
        const data = cache.readQuery({
          query: fieldMappingsGQL,
          variables: { type, integration },
        });
        cache.writeQuery({
          query: fieldMappingsGQL,
          variables: { type, integration },
          data: { fieldMappings: [...data.fieldMappings, createFieldMapping] },
        });
      },

Seems like it has something to do with immutability.

Here's another example with writing for a delete mutation:

Not updating after mutating:

      update: cache => {
        const data = cache.readQuery({
          query: fieldMappingsGQL,
          variables: { type, integration },
        });
        data.fieldMappings = data.fieldMappings.filter(o => o.id !== id);
        cache.writeQuery({
          query: fieldMappingsGQL,
          variables: { type, integration },
          data,
        });
      },

Works again:

      update: cache => {
        const data = cache.readQuery({
          query: fieldMappingsGQL,
          variables: { type, integration },
        });
        cache.writeQuery({
          query: fieldMappingsGQL,
          variables: { type, integration },
          data: {
            fieldMappings: data.fieldMappings.filter(o => o.id !== id),
          },
        });
      },

So I'm pretty confused about why this works, but I'm hoping this account helps someone else or helps Apollo fix a bug.

@quolpr
Copy link

quolpr commented Mar 26, 2019

Hmm, not sure that I have a similar issue. But, when I put writeData to setTimeout it works correctly:

<ApolloConsumer>
  {client => {
    const navbarState: State = {
      header,
      backPath: backPath || "",
      caption: caption || "",
      __typename: "Navbar"
    };

    setTimeout(() => {
      // Not sure why, but without setTimeout
      // NavbarContainer doesn't get updated
      client.writeData({ data: { navbar: navbarState } });
    }, 0);

    return null;
  }}
</ApolloConsumer>

Other components are updated correctly with new state from writeData

@charles-leapyear
Copy link

@rchasman I observed the same thing...

all our update functions were operating on data directly and then passing it to writeQuery (I think this came from examples in their old docs) and when I upgraded to the latest version, they all stopped working so there must be something under the hood in relation to immutability 😭

@U-4-E-A
Copy link

U-4-E-A commented Apr 2, 2019

@charles-leapyear - thanks for that hint. I was tearing my hair out with this last night, woke up this morning to see your comment. I too was working directly on the data object. Switched over to using const newData = {...data} and it works perfectly now. Hope this might help someone. Cheers!

@akamos
Copy link

akamos commented Apr 6, 2019

Thats a real serious thing! I started to use Apollo and was struggling with this a whole day... Thanks for all tips here above!

@Rennzie
Copy link

Rennzie commented Apr 10, 2019

Thanks for the tips here everyone. Had the same issue but solved it using @rchasman's method. It didnt work at first but realised I had forgotten to use the variables for the query when using writeQuery()

@kiano0sh
Copy link

I don't know why apollo team doesn't do anything for this problem, Anyway i solved it as @good-idea said by changing client.cache.writeQuery() to client.writeQuery()

@hwillson
Copy link
Member

Please see #4398. Long story short, this isn't a bug - it's by design. Hopefully PR #4664 helps address this in the docs, but if anything still isn't clear, please let us know (or better yet, fire over a new PR). Thanks!

@brandonmp
Copy link

probably not suitable for all use cases, but i worked around this by adding pollInterval={250} to my query component.

though to be fair that particular query component is only fetching client state. i doubt this would work if you were mixing local / remote in the same query.

@JaosnHsieh
Copy link

JaosnHsieh commented Jul 30, 2019

Query component data updated works after added corresponding variables options either on client.writeQuery or client.cache.writeQuery.

Query componen t

<Query query={POSTS} variables={{ limit: 5, offset: 0 }}>
{({ data }) => (
            {JSON.stringify(data)}
)}
</Query>

withApollo wrapped component

data will NOT get updated

const { client = {} } = props;
      const { posts = [] } = client.cache.readQuery({
        query: POSTS,
        variables: { limit: 5, offset: 0 },
      });
      const { data: { post = {} } = {} } = await client.query({
        query: POST,
        variables: { id: postId },
      });
      client.cache.writeQuery({
        query: POSTS,
        data: { posts: [post, ...posts] },
      });

data UPDATE WORKS!!

const { client = {} } = props;
      const { posts = [] } = client.cache.readQuery({
        query: POSTS,
        variables: { limit: 5, offset: 0 },
      });
      const { data: { post = {} } = {} } = await client.query({
        query: POST,
        variables: { id: postId },
      });
      //client.writeQuery works as well
      client.cache.writeQuery({
        query: POSTS,
        // this variables must be added
        variables: { limit: 5, offset: 0 },
        data: { posts: [post, ...posts] },
      });
    "react-apollo": "^2.5.8",
    "apollo-boost": "^0.4.3" //    includes  apollo-cache-inmemory "^1.6.2"

@ronatory
Copy link

I checked/tried everything from the solutions here, but in the end I did something else wrong. See here

@alexmiddeleer
Copy link

alexmiddeleer commented Aug 18, 2019

I don't think this is fixed by the documentation change #4398. I am writing to the cache exactly as explained in the tutorial and it's not working. Have tried fiddling with immutability as per above comments and adding the variables field, and none of the approaches work. This only started happening when I updated apollo client from 2.4.* to 2.6.*.

Hopefully in the future we can get an api that is less prone to user error. Something like cache.updateCacheForPreviousQuery which pops up a sensible error message if a previous query matching the parameters was not found. Perhaps that's a convoluted approach, but there must be something a bit less tricky we could do. Thanks to the contributors for this project.

@alexmiddeleer
Copy link

If anyone else is at the bottom of this thread and still confused... you really do need a deep clone of the new data object as per @rchasman 's investigation. But you also need to make sure you provide variables as per @JaosnHsieh . Neither of things were required in a previous version of react-apollo.

@obedm503
Copy link

obedm503 commented Nov 8, 2019

@Keksinautin deep cloning the whole tree seems like a bad idea since it will rerender the entire tree. it's better to just change what actually changed. It's the same pattern used in redux

@focux
Copy link

focux commented Nov 8, 2019

@obedm503 Apollo states in his docs to not update the returned value of readQuery, so deep cloning is the right solution and I don't think it will re-render the whole app tree.

@obedm503
Copy link

obedm503 commented Nov 8, 2019

When I said similar to redux, I meant something like this #3909 (comment)

deep cloning will rerender not the whole app but, the whole tree under the components that use the query

@focux
Copy link

focux commented Nov 8, 2019

Well, that solution didn't work for me, I had to deep clone the readQuery object for it to re-render in the components it had to.

@Keksinautin
Copy link

@Keksinautin deep cloning the whole tree seems like a bad idea since it will rerender the entire tree. it's better to just change what actually changed. It's the same pattern used in redux

@obedm503 thank you, that is an interesting point. I may be wrong, please correct me if so, but as I know React does not compare states especially by link during the change detection, it compares the virtual dom and by value. This means that only really changed parts of the dom-tree will be rerendered.
https://blog.angularindepth.com/what-every-front-end-developer-should-know-about-change-detection-in-angular-and-react-508f83f58c6a

@obedm503
Copy link

obedm503 commented Nov 8, 2019

@Keksinautin you may be right, since it's within the same component

@JesseZomer
Copy link

We're seeing the opposite of this problem in a project with Angular Apollo Client. We've got a calendar component of sorts, which consists of week components which have day components which have appointment components. All these components are onPush components except for the parent.

Whenever we do a mutation, in the update we do a proxy.readQuery to get the current data. We change the appointment object in the week where it needs changing and write back the data with a proxy.writeQuery.

Now what I expect to happen is that because all the other weeks and days and appointments didn't change, the onChanges for those components shouldn't be called. However it goes through every components onChanges, as if all the Input properties (week/day/appointment objects) were different objects.

Does Angular Apollo Client create new objects for every object you give to writeQuery to prevent the problem in this issue (nothing updates, because the object is still the same object)? How to prevent all my components from going through the onChanges?

@yentzu-wang
Copy link

yentzu-wang commented Dec 9, 2019

I also had the same problem. According to the official documentation local storage management section, it says use client.readQuery to get the data from cache. I intuitively used the function to get and destructure the data and bound the value to components. When I updated the cache using cache.writeQuery, the components never re-render. After tried and error, I found out that because the client hook itself has never changed and the function - client.readQuery is just a reference pointing somewhere to a function which returns an object. In the case, you should use useQuery hook to get the data from cache instead of cache.readQuery. Just remember to add @client directive to your query. I think it will solve your problem. At least I used this to solve mine.

@semanser
Copy link

Short answer: just use client.writeQuery instead of cache.writeQuery when you want your changes to be reflected in the UI immediately.

Long answer (from docs):

  1. The cache you created with new InMemoryCache(...) class is not meant to be used directly, but passed to the ApolloClient constructor. The client then accesses the cache using methods like readQuery and writeQuery. The difference between cache.writeQuery and client.writeQuery is that the client version also performs a broadcast after writing to the cache. This broadcast ensures your data is refreshed in the view layer after the client.writeQuery operation. If you only use cache.writeQuery, the changes may not be immediately reflected in the view layer. This behavior is sometimes useful in scenarios where you want to perform multiple cache writes without immediately updating the view layer.

  2. The update function receives cache rather than client as its first parameter. This cache is typically an instance of InMemoryCache, as supplied to the ApolloClient constructor whent the client was created. In case of the update function, when you call cache.writeQuery, the update internally calls broadcastQueries, so queries listening to the changes will update. However, this behavior of broadcasting changes after cache.writeQuery happens only with the update function. Anywhere else, cache.writeQuery would just write to the cache, and the changes would not be immediately broadcast to the view layer. To avoid this confusion, prefer client.writeQuery when writing to cache.

Source: https://github.com/apollographql/apollo-client/pull/4664/files

@nadzic
Copy link

nadzic commented Feb 20, 2020

Hi guys! I still have the issue, I tried to use client instead of cache but still the same - the cache gets updated, but it does not update our components. We are using the following code:

  const handleCreateSolution = async (solutionTitle, solutionDescription) => {
    try {
      const { data } = await createSolution({
        variables: {
          solution: {
              title: solutionTitle,
              description: solutionDescription
            }
        },
        update(cache, { data: { createSolution } }) {
          const { solutions } = cache.readQuery({ query: GET_SOLUTIONS });
          client.writeQuery({
            query: GET_SOLUTIONS,
            data: {
              solutions: solutions.concat([createSolution]) },
          });
        }},
      );
      handleCancel();
    } catch (e) {
      setError(e)
    }
  }

Is any solution already out there for it? Becuae otherwise we have to always refresh the page to get correct data for us.

@lsliwaradioluz
Copy link

@nadzic I was in the same boat, using client.writeQuery instead of cache.writeQuery, deep cloning and other stuff helped me none. Only the solution provided by @rchasman worked. I think you should give it a try.

I was just tearing my hair out over a regression where an existing component doesn't consistently update anymore after a mutation call, update : writeQuery.

I tested render, and it appears not to receive new props after a writeQuery from the mutation update even though the cache seems to be right (but sometimes it does work).

I played around a bit and was relieved (and confused) to see it behaving properly and consistently after some very minor changes making the data write immutable. Here's some examples:

Bad, flaky component re-rendering:

      update: (cache, { data: { createFieldMapping } }) => {
        const data = cache.readQuery({
          query: fieldMappingsGQL,
          variables: { type, integration },
        });
        data.fieldMappings.push(createFieldMapping);
        cache.writeQuery({
          query: fieldMappingsGQL,
          variables: { type, integration },
          data,
        });
      },

These very minor changes (can you spot them?) made the mutation update work flawlessly again:

      update: (cache, { data: { createFieldMapping } }) => {
        const data = cache.readQuery({
          query: fieldMappingsGQL,
          variables: { type, integration },
        });
        cache.writeQuery({
          query: fieldMappingsGQL,
          variables: { type, integration },
          data: { fieldMappings: [...data.fieldMappings, createFieldMapping] },
        });
      },

Seems like it has something to do with immutability.

Here's another example with writing for a delete mutation:

Not updating after mutating:

      update: cache => {
        const data = cache.readQuery({
          query: fieldMappingsGQL,
          variables: { type, integration },
        });
        data.fieldMappings = data.fieldMappings.filter(o => o.id !== id);
        cache.writeQuery({
          query: fieldMappingsGQL,
          variables: { type, integration },
          data,
        });
      },

Works again:

      update: cache => {
        const data = cache.readQuery({
          query: fieldMappingsGQL,
          variables: { type, integration },
        });
        cache.writeQuery({
          query: fieldMappingsGQL,
          variables: { type, integration },
          data: {
            fieldMappings: data.fieldMappings.filter(o => o.id !== id),
          },
        });
      },

So I'm pretty confused about why this works, but I'm hoping this account helps someone else or helps Apollo fix a bug.

@tomasyaya
Copy link

@nadzic are you sure the data object in the client.writeQuery is exactly the same as the one being received by the query?
Also, in the update function cache.write will broadcast the changes to your components, I think it's not necessary to use the client.

@julian-linux
Copy link

julian-linux commented Apr 17, 2020

u can use cache.writeQuery.
I think The problem is due Object.is comparasion in pure components or hooks.
I solved the problem using "immutable" logic in data.
lodash.cloneDeep works very good for this.

update: (cache, { data: { createTodo } }) => {
      const data = _.cloneDeep(cache.readQuery({ query }));
      data.todos.push(createTodo);
      cache.writeQuery({ query, data });
    },

@ghost
Copy link

ghost commented Apr 22, 2020

I just use immerJs for change data, and then pass it to writeQuery
answer in this post
https://stackoverflow.com/a/46124956/12469873

@andrewmclagan
Copy link

andrewmclagan commented Apr 29, 2020

Wow... this is a really really really poorly documented issue in Apollo.

If anyone is using hooks there is a change that you need to add the { returnPartialData: true } option to the useQuery call on the dependant item containing the list. Check the API docs

  const { data, ...rest } = useQuery(SOME_QUERY, {
    variables: { uuid },
    returnPartialData: true
  });

One comment i will make about Apollo, its documentation is very poor. Time and time again i see so many developers struggling with simple issues that are due to a lack of documentation. Otherwise its an amazing library.

@jesper-bylund
Copy link

I've tried all of these and NONE of them work for me. So the next question is: how can you verify if the update issue is due to writeQuery not triggering an update, or is useQuery is not updating?

Or stated in other terms: is there any way of debugging if your cache is actually updating properly from a writeQuery without having a query firing?

@obedm503
Copy link

obedm503 commented May 8, 2020

@raelmiu do you have the Apollo devtools installed?

@jesper-bylund
Copy link

@obedm503 yes, I do.

@obedm503
Copy link

obedm503 commented May 8, 2020

@raelmiu you can inspect the cache before and after a read or write through the devtools

@benjamn
Copy link
Member

benjamn commented May 8, 2020

Hey folks, if you're using @apollo/client (AC3), please give the latest beta version (3.0.0-beta.46) a try, as we've made some substantial improvements to the automatic broadcast of cache changes (#6221 recently, and #6050 before that, in beta.40).

I can't begin to say this update will solve all the problems that have been raised in this thread, but there's a reasonable chance that many of them will be fixed/improved, I think.

Thanks for everyone's patience while we fix this behavior once and for all.

@Adzz
Copy link

Adzz commented May 19, 2020

For me, I had to include variables into the query:

const MEAL_COMPONENTS = gql`
  query MealComponents($active: Boolean) {
    mealComponents(active: $active) {
      id
      name
      sensitivityInformation {
        id
        sensitivityName
        present
      }
    }
  }
`;
const MEAL_COMPONENTS_SUBSCRIPTION = gql`
  subscription Meals {
    mealComponentsSubscription {
      id
      name
      active
      sensitivityInformation {
        id
        present
        sensitivityName
      }
    }
  }
`;
  useSubscription({
    subscription: MEAL_COMPONENTS_SUBSCRIPTION,
    variables: {},
    onSubscriptionData: ({ client, subscriptionData }) => {
      setNewMealToCheck(true);

      client.cache.writeQuery({
        query: MEAL_COMPONENTS,
       // You need to add any variables the query makes here:
        variables: { active: true },
        data: { mealComponents: subscriptionData.data.mealComponentsSubscription },
      });
    },

@maltenz
Copy link

maltenz commented May 29, 2020

On man, I think I am gonna give up on apollo state after working on it for over a week! I almost managed to fix this issue by using
returnPartialData: true
Which works great if you are switching between views on the same stack in Expo, but the moment you unmount the view (like in a modal) the whole thing goes back to its initial state.
Back to redux so I can spend my time working on my important stuff!

@julian-linux
Copy link

@maltenz men u can use lodash.cloneDeep

@ghost
Copy link

ghost commented Jun 28, 2020

this was a pain in the ass.

BUT

client.cache.writeQuery({}) won't work !!!

you have to use client.writeQuery({})

@Javey
Copy link

Javey commented Jul 15, 2020

I have the same problem, but the reason is that I called client.stop() by mistake.

@LucaProvencal
Copy link

I overlooked this for far too long: make sure you are on the latest Apollo version. I switched from apollo-client ^2.6.3 to @apollo/client ^3.2.5 and made sure I am importing ApolloClient as follows:

import { ApolloClient } from '@apollo/client';

None of the above solutions were working for me until I did this. Cache updating appears to be working now.

@nateq314
Copy link

nateq314 commented Apr 21, 2021

Tried everything above and nothing was working. Upgraded from 3.1.5 to 3.3.15 (latest as of time of writing) per @LucaProvencal 's suggestion above and the problem disappeared.

@skworden
Copy link
Contributor

skworden commented Aug 23, 2021

Took me a couple hours but got this to work for Apollo - 3.4.8 (latest as of this post). Below are my observations and this seems to be working across all CRUD operations. I tried to make the examples as verbose as possible.

  • A mutation's cache update must use the same query as the query that is being updated
  • A mutation's cache update must use the same variables - if none were used they can be empty
  • A cache update query must return the same attributes as the original query
  • The useQuery hook and the useMutation hook must be in the same component rendering lifecycle - i.e. useQuery can't be in a parent component because it won't get rerendered and the changes won't be visible (cache is still updated).
  • Merge functions are needed in the apollo config for certain mutations - the console will show a warning telling you when you need them.
  • Update mutation must return the key that is used for cache (id - if you didn't set it) and other attributes used in the original query - it will automatically update cache - no need to add an update function to the mutation
  • Delete mutation - must remove the item for the cache manually

I'd stick to the above rules until you got it working - it appears that they don't all apply in all situations -but that is getting too specific to this. It's a good general list to follow.

Example query

export const GET_TODOS = gql`
  query todos($relatedId: String!) {
    questionSet(where: { relatedId: { _eq: $relatedId } }) {
      id
      name
      relatedId
    }
}`;

Example useQuery Hook

  const { data, error, loading } = useQuery(GET_TODOS, { variables: { relatedId: '123' }});

Example Mutation Update

export const CREATE_TODO = gql`
  mutation CreateTodo($relatedId: String, $name: String) {
    create_Todo(_set: { relatedId: $relatedId, name: $name }) {
      returning {
        // since the query returned the below three attributes - they must all be set in the cache update in the mutation update
        id
        name
        relatedId
      }
    }
  }`;

Example useMutation hook

  const [create] = useMutation(CREATE_TODO, {
    update: (cache, { data }) => {
      if (data) {
        const cached = cache.readQuery({
          query: GET_TODOS, // must be the same query as the useQuery hook
          variables: { relatedId: '123' }, // must use the same variables as the useQuery hook - they can be empty if none were used
        });
        const newData = data.returning;
        const cachedData = cached ? cached.todos: [];
        cache.writeQuery({
          data: { todos: [...cachedData, ...newData] }, // if newData is at the end - the item will be pushed to the end of the list - move newData to the first position [ ...newData, ...cachedData] if you want it to be first on the list
          query: GET_TODOS // must be the same query as the useQuery hook
          variables: { relatedId: '123' }, // must use the same variables as the useQuery hook - they can be empty if none were used
        });
      }
    },
  });

Example useMutation delete

...See_create_exmaple_for_other_config
update: (cache) => {
      const cachedData = cache.readQuery({ query: GET_TODOS, variables:  { relatedId: '123' }});
      const filteredData =  cachedData && cachedData[gqlKey].filter((cached) => {
          if (cacheId) {
            const removedId = getKeyValue(cached, cacheId);
            return removedId !== id;
          }
          return cached.id !== id;
        });
      cache.writeQuery({ query: GET_TODOS, data: { todos: filteredData }, variables: { relatedId: '123' }});
    },

Example merge functions for generic and query specific

new ApolloClient({
    cache: new InMemoryCache({
      typePolicies: {
        todos: {
          merge(_, incoming) {
            return incoming;
          },
        },
        Query: {
          fields: {
            merge(_, incoming) {
              return incoming;
            },
          },
        },
      },
    }),
    ...other_config
  });

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 1, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests