apollo-client: update mutation doesn't work

Intended outcome: updated list of recipes be rendered

Actual outcome: the old list get rendered

How to reproduce the issue: follow what comes here !!!

Versions

hi I have the following client

import React ,{Component}from 'react'
import { Mutation } from "react-apollo";
import {ADD_RECIPE} from '../../mutations';
import Error from '../Error';
import {withRouter} from 'react-router-dom';
import {GET_ALL_RECIPIES} from '../../queries'

class AddRecipe extends Component {
  ...
  onSubmit(event,addRecipe){
    event.preventDefault();
    addRecipe().then(
      ({data})=>
        {
          this.props.history.push("/")
        }
      )
  }
  render (){
    const{name,category,description,instruction,username} = this.state;
    return(<div className="App">
      <h2>Add recipe</h2>
      <Mutation
             mutation={ADD_RECIPE} 
             variables={{name,category,description,instruction,username}} 
             update={(cache, {data:{addRecipe}}) => {
                  const {getAllRecipes} = cache.readQuery({ query: GET_ALL_RECIPIES });
                  console.log(cache)
                  //console.log('get all recipes',getAllRecipes)
                  //console.log('add recipe',addRecipe)
                  cache.writeQuery({
                    query:GET_ALL_RECIPIES,
                    data:{getAllRecipes:getAllRecipes.concat[addRecipe]}
                  })
                  console.log(cache)
              }} >
      {(addRecipe, {data,loading,error})=>
        
           (<form className="form" onSubmit={event=>this.onSubmit(event,addRecipe)}>
                  <input type="text" name="name" onChange={this.handleChange.bind(this)} placeholder="recipe name" value={name}/>
                  <select name="category" value="breakfast" id="" onChange={this.handleChange.bind(this)} value={category}>
                    <option value="breakfast">breakfast</option>
                    <option value="lunch">lunch</option>
                    <option value="dinner">dinner</option>
                    <option value="snack">snack</option>
                  </select>
                  <input type="text" name="description" onChange={this.handleChange.bind(this)} placeholder="recipe description" value={description}/>
                  <textarea name="instruction" id="instruction" cols="30" rows="10" onChange={this.handleChange.bind(this)} value={instruction}> </textarea>
                  <button type="submit" className="button-primary" disabled = {loading || this.validateForm()}>submit</button>
                  {error && <Error error={error}/>}
            </form>)
        
      }
      </Mutation>
    </div>)
  }
}
export default withRouter(AddRecipe)

and for get Recipe is:

import gql from 'graphql-tag';


//RECIPE QUERY
export const GET_ALL_RECIPIES =gql`
query {
    getAllRecipes{
        name
        _id
        category
        
    }
}
`

and get recipe item is :

import gql  from "graphql-tag";

export const GET_RECIPE_ITEM =gql`
query($id:ID!){
        getRecipeItem(_id:$id){
    				
          _id
          name
          category
          description
          instruction
          createdDate
          likes
          username
		}
}`

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 29 (5 by maintainers)

Commits related to this issue

Most upvoted comments

Why is this issue still opened? It was resolved for me by installing the latests versions of apollo-client and apollo-cache-inmemory

npm install apollo-client@latest apollo-cache-inmemory@latest

Was having the same issue with the UI not reflecting the updated cache. Following the example in the docs, I could not get it working unless I created a new array for data.todos:

    options: {
      update: (proxy, { data: { createTodo } }) => {
        const data = proxy.readQuery({ query });
        // data.todos.push(createTodo);
        data.todos = [...data.todos, createTodo];
        proxy.writeQuery({ query, data });
      },
    },
"apollo-cache-inmemory": "^1.6.3",
"apollo-client": "^2.6.4",

Same issue, using “apollo-cache-inmemory”: “^1.6.2”, “apollo-client”: “^2.6.2”,

@dsanders11 To clarify, modifying result objects returned by readQuery only affects the cached result objects, not the data in the store, which can only be modified by writing new results.

Modifying data and then writing it back to the store is safe, since it invalidates the previous result objects, so readQuery will return new objects next time. That’s what the official docs are doing, unless I’m mistaken.

The problem with a deep copy is that it’s expensive, both because the copy itself takes time, and because identical results are no longer === identical, which makes equality comparisons in your application (e.g. to determine whether to re-render a component) more expensive. Making a deep copy is something you (as the consumer of the cache) can choose to do, if you want to pay for that convenience / peace of mind. In practice, when you copy your results, you don’t usually need a full deep copy, because you can get away with shallow-copying parts of the result and modifying the partial copy without affecting the original object. Since the cache doesn’t know what parts of the data you’re planning to change, it has no hope of making these decisions correctly. Only you understand the needs of your application. The job of the cache is to enable all use cases, without making assumptions about where you might value safety over performance. If the cache itself always made a deep copy whether or not it was necessary, developers who don’t want/need the copying behavior would be stuck with it anyway. In this area of the system, we’ve chosen to prioritize performance and developer choice over eliminating the possibility of misuse.

For those reasons, I think freezing the result objects is the only viable option here, though that would interfere with valid read-modify-write patterns, and would definitely require changing the documentation.

Yup, always ensure the original cache object is immutable! Assign new value by deep clone the object and assign new values will work!

FYR, "@apollo/client": "^3.0.0-beta.49" When I use update like this, it does not work.

client.mutate({
// mutation,

update(cache){
  // const query;
  const data = cache.readQuery({ query });

  data.array.push({ key: value });

  cache.writeQuery({
    query,
    data,
  });
},

After I set a new object to data like this, it works.

update(cache){
  // ...
  cache.writeQuery({
    query,
    data: { array: [...data.array, { key: value }] },
  });
}

Not sure if this helps, but I found it works if my collection didn’t have id’s returned. When it did have id’s I had to write a fresh object.

apollo-client@2.6.4
@apollo/react-hooks@3.1.2
apollo-boost@0.4.4
apollo-cache-inmemory@1.6.3

eg

// works with just re-creating array when no id's
export const GET_BOOKS = gql`{ 
  books { 
    title 
    author
  } 
}`

addBook({
  variables: {input:{ title: title.value, author: author.value }},
  update: (store, { data: { book } }) => {
    const data = store.readQuery({ query: GET_BOOKS }) // read books from cache
    data.books = [ ...data.books, book] // create new array with our book
    store.writeQuery({ query: GET_BOOKS, data}) // write back data
  },
})
// with id, new data object needed
export const GET_BOOKS = gql`{ 
  books { 
    id
    title 
    author
  } 
}`

addBook({
  variables: {input:{ title: title.value, author: author.value }},
  update: (store, { data: { book } }) => {
    const data = store.readQuery({ query: GET_BOOKS }) // read books from cache
    const books = [ ...data.books, book] // create new array with our book
    store.writeQuery({ query: GET_BOOKS, data:{...data,books} }) // write back new object with our books
  },
})

@benjamn why are result objects returned by readQuery not consistent with the data in the store? readQuery don’t get data from the store, but return the cached result objects,so if i modify some items, there is no need to writeQuery to update the store,modifying the result objects is just enough。it seems that there are two cache layers,one is the result objects returned by readQuery, the other one is the store. This really makes me confused. I also think the result objects should be immutable and frozen, or readQuery should return data from the store every time instead of return the result objects.