apollo-client: Can't find field on object (ROOT_QUERY) undefined.

I’m using ReactNative, and am able to load a user object from my graphql server using

graphql(gql`{
    user(id: 1) {
        first_name
    }
}`)(DisplayComponent)

Therefore, I would expect this user to be in the cache. However, when I write the following code in the constructor of a different component:

apolloClient.readQuery({query: gql`
    query Test {
        user(id: 1) {
            first_name
        }
    }
`})

I get the following error: Can't find field user({"id":1}) on object (ROOT_QUERY) undefined.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 32
  • Comments: 42 (6 by maintainers)

Most upvoted comments

For what it’s worth, I ran into the same issue and found out that I forgot the variables when calling readQuery:

const updateFunc = (cache, {data: {saveLibrary}}) => {
    const {libraries} = cache.readQuery({query: libQuery, variables: {id: saveLibrary.id}});
};

Maybe it can help someone 😃

Closing this for lack of activity. If it’s still an issue, please provide a reproduction and we’ll reopen. Thanks! 🙂

For those who try to update the cache after a mutation.

I had this error today and resolved it.

Basically, if your mutation is on a page “create-post” and it create a post. That’s great, you will have access to ROOT_MUTATION in the cache.

But this page just create a post… it doesn’t show the posts. So there are no query(getPosts) that are triggered.

In that case there are no ROOT_QUERY in the cache.

So when cache.writeQuery (or store.writeQuery) try to find the data inside ROOT_QUERY, it throw an error(why dont you just tell us that my query dont exist in the cache!!!).

But well… it’s ok.

Because if the user didn’t tiger the query you dont need to update the cache. The user will trigger the query and fetch the data (and store it in the cache) when he/she will be on the page with that query (“posts” or “blog”).

The solution:

You can do a try and catch but i dont recommend it, because you are “trying” to update and if you fail you have to throw an error. But if you make a condition, like:

if(cache.data.data.ROOT_QUERY){
    // Update the cache here!!!
    // ROOT_QUERY is inside the data that is inside the cache
    // You can now update the cache here!!!!!
}

// You could do this aswell:

if(!cache.data.data.ROOT_QUERY){
    // an error or anything you want to be done
}else{ 
    //Update here!!! 
}

And that’s it!!! In the end your worst enemy was you and the refresh button(it clear the cache).

yeah I’m having the same issue. My one complaint about apollo is that simple CRUD stuff is not as turn key as you’d like.

I’ve had the same problem

....
Error: Can't find field projects({}) on object (ROOT_QUERY) {
  "projects({\"filter\":null})": 
....

This happens when I try to update the store when I add an item and I’m sure they’re loaded.

@graphql(createProject, {
    name: "createProject",
    options: {
        update: (store, { data: { createProject } }) => {
            try {
                const newData = store.readQuery({ query: getProjects });
                newData.projects.push(createProject);
                store.writeQuery({ query: getProjects, data: newData });
            } catch (e) {
                console.log(e);
                console.log("Not updating store - Projects not loaded yet");
            }
        }
    }
})

I’ve tried removing the filter field which I thought might cause the problem but it doesn’t make a difference.

The problem was my optimistic response didn’t contain the same fields as the ones in the strore. Which seems weird but that fixed it.

@AlexLeung it looks like it should work. It will be helpful if could you provide a reproduction using the react-apollo-error-template

@helfer, @cesarsolorzano: This thread became a mess, wonder why there was no definitive explanation of the nature of this issue from apollo’s team before you closed it, I will give yet another explanation as I feel the comments here do not clearly explain the original issue.

How Apollo caches things: Granular object-based caching

Apollo maintains a list of ids of every object that was returned in a query for every combination of query/query-parameters. cache.data.data.ROOT_QUERY will store those (call it querysets): with keys being queries with its parameters and cached object ids lists as values.

Why does Can't find field on object (ROOT_QUERY) appears

If cache.readQuery is called with a query that was not dispatched previously or a query with a different parameter combination to a previously dispatched, ROOT_QUERY object will not contain an object-id ref list for a key that looks the following way: your_query_field({"param_1":"smt_here","param2":"smt_here"}), so the exception will be thrown.

Now, this exception does not necessarily imply error, all it says is that there was no query made with combination of such parameters. When our application logic cannot guarantee a given query to be called prior to this cache.readQuery we may just handle this expection and give a sensible empty default.

Another possible solution, use writeFragment

In my scenario, there is a user query which can be used to get a user’s data from the database. When visiting a viewer’s profile, it looks like this:

	query UserQuery($username: String, $userId: String) {
		user(username: $username, _id: $userId) {
			_id
			displayName
			username
			location
			bioLine
			website
			twitter
			instagram
			isFollowedByViewer
			followingConnection {
				edges {
					node {
						_id
						displayName
						username
						avatar {
							publicId
						}
					}
				}
			}
			followersConnection {
				edges {
					node {
						_id
						displayName
						username
						avatar {
							publicId
						}
					}
				}
			}
			avatar {
				publicId
			}
		}

Elsewhere in the site, there is a list of user ‘Follow Buttons’. The buttons only need to know two things about the user: their _id, and if the current viewer follows them. They each use the query like so:

	query UserQuery($username: String, $userId: String) {
		user(username: $username, _id: $userId) {
			_id
			isFollowedByViewer
		}
	}

This query is pretty lean, because I might be loading a list of +100 Follow Buttons, and I don’t need or want all of the extra information.

The followUser and unFollowUser mutations look like this: (Just showing one for brevity, but they’re more or less identical)

⚠️ doesn’t work:

const mutation = gql`
	mutation FollowUser($userId: String!) {
		followUser(id: $userId) {
			followedUser {
				_id
				username
			}
		}
	}
`

const config = {
	name: 'followUser',
	options: (props) => {
		return {
			variables: {
				userId: props.user._id,
			},
			update: (cache, { data }) => {
				// Instead of re-fetching the affected user's query,
				// We'll update it optimistically since we know that:
				// - The author now follows the user
				const { followedUser } = data.followUser

				// Update the unfollowed user's data from the query

				const query = {
					query: userProfileQuery,
					variables: {
						userId: followedUser._id,
					},
				}

				const followedUserFromCache = cache.readQuery(query) // !! Breaks here
				cache.writeQuery({
					...query,
					data: {
						user: {
							...followedUserFromCache.user,
							isFollowedByViewer: true,
						},
					},
				})
			},
		}
	},
}

That throws this error:

Error: Can't find field location on object (5ac4048fe25ff14111279fcf) {
  "_id": "5ac4048fe25ff14111279fcf",
  "displayName": "Joseph Thomas",
  "username": "good-idea",
  "avatar": {
    "type": "id",
    "generated": true,
    "id": "$5ac4048fe25ff14111279fcf.avatar",
    "typename": "Image"
  },
  "__typename": "User",
  "isFollowedByViewer": false,
  "followersConnection": {
    "type": "id",
    "generated": true,
    "id": "$5ac4048fe25ff14111279fcf.followersConnection",
    "typename": "UserSocialConnection"
  }
}.

If I had previously visited good-idea’s profile, this query would work, because the item in the cache would have location and the other fields.

Instead of using cache.readQuery, then cache.writeQuery, I can use .writeFragment.

works!

const mutation = gql`
	mutation FollowUser($userId: String!) {
		followUser(id: $userId) {
			followedUser {
				_id
				username
			}
		}
	}
`

const config = {
	name: 'followUser',
	options: (props) => {
		return {
			variables: {
				userId: props.user._id,
			},
			update: (cache, { data }) => {
				// Instead of re-fetching the affected user's query,
				// We'll update it optimistically since we know that:
				// - The author now follows the user
				const { followedUser } = data.followUser

				// Update the unfollowed user's data from the query

				cache.writeFragment({
					id: followedUser._id,
					fragment: gql`
						fragment unFollowedUser on User {
							isFollowedByViewer
						}
					`,
					data: {
						isFollowedByViewer: true,
						__typename: 'User',
					},
				})
			},
		}
	},
}

I imagine there’s more outside of the code I’ve provided above that could affect things. But, I hope this is helpful to someone else who comes here with the same question!

@helfer @cesarsolorzano, I had the exact same problem and I think I found the problem. Given how the store key is resolved in https://github.com/apollographql/apollo-client/blob/master/src/data/storeUtils.ts#L163, any GraphQL query that has an null argument, will cause this error.

The keys in the store can look like this $ROOT_QUERY.whatever.foo.bar({"foo":"bar","baz": NULL}), but when the readStoreResolver tries to find them in the store, it looks for $ROOT_QUERY.whatever.foo.bar({"foo":"bar"}) because the NULL values are undefined at this point, and will be thrown away by JSON.stringify().

I’d like to know how to handle scenarios very much similar to this one as well. What I have is a component that triggers a mutation and then updates the store with the result of the mutation via readQuery/writeQuery. Pretty trivial scenario. The problem arises when I attempt to readQuery from the store with a query that has not yet been loaded. Then I get the exception Can't find field <field> on object (ROOT_QUERY).

Wrapping the block of code that handles the updates with try/catch block like @JannesV did here https://github.com/apollographql/apollo-client/issues/1701#issuecomment-308078150 would fix the issues. Would you recommend that as a viable option?

For those who do not use “readQuery” but “readFragment” and run into the same issue, here’s the relevant part of the docs over here (https://www.apollographql.com/docs/react/advanced/caching.html#readfragment):

If a todo with that id does not exist in the cache you will get null back. If a todo of that id does exist in the cache, but that todo does not have the text field then an error will be thrown.

Make sure you do not try to read a Fragment containing fields that haven’t been originally queried and saved to the cache by another query before. This will cause readFragment to find an object in the cache with a matching ID but without the field which will throw an error.

@xSilverGunx 😕 Not in all situations. I have default state defined, and it still trows this error. This is interesting ride on the edge, i shod switch to facebook stack. This is so unpredictable.

Can someone help me with this bug, I got stuck with this error as well and it turned out to be totally unrelated to any of the above. ##ERRORS

Invariant Violation: Can't find field me({}) on object {
  "me({\"id\":\"1\"})": {
    "type": "id",
    "generated": true,
    "id": "$ROOT_QUERY.me({\"id\":\"1\"})",
    "typename": "Profile"
  },
  "me({\"id\":1})": {
    "type": "id",
    "generated": true,
    "id": "$ROOT_QUERY.me({\"id\":1})",
    "typename": "Profile"
  }
}.

##MY CODE /gql/index.js file

---
import gql from 'graphql-tag'
---
export const USER_PROFILE_QUERY = gql`
    query me($id: ID!) {
      me(id: $id) {
          bio
          dob
          personalInfo{
            address
          }
          user{
            created_at
        }
      }
    }`
export const UPDATE_PROFILE_MUTATION = gql`
    mutation updateProfileInfo($id:ID!, $show: String, $address:String, $phone_number:String, $nationality:String,$sex:String,$marital_status:String){
      updateProfileInfo(id: $id, show: $show,address:$address,phone_number:$phone_number,nationality:$nationality,sex:$sex,marital_status:$marital_status){
          address
          profile{
            bio
            user{
              created_at
            }
          }
      }
    }`

/profileInfo.vue file

---
import { USER_PROFILE_QUERY, UPDATE_PROFILE_MUTATION } from '~/graphql/'
---
 apollo: {
        // fetch profile by ID
        me: {
            query: USER_PROFILE_QUERY,
            variables () {
                return {
                    id: this.id
                }
            }
        }
    },
    methods: {
      update() {
            this.$apollo
                .mutate({
                    mutation: UPDATE_PROFILE_MUTATION,
                    variables: {
                      id: this.id,
                      bio: this.bio,
                      address: this.address
                    },
                    update: (store, { data: { updateProfileInfo } }) => {
                        // read data from cache for this query
                        const {data} = store.readQuery({
                          query: USER_PROFILE_QUERY,
                          variables () {
                                return {
                                    id: this.id
                                }
                            }
                        })

                        console.log(data)
                        // add new profile from the mutation to existing profiles
                        data.me.push(updateProfileInfo)

                        // write data back to the cache
                        store.writeQuery({ query: USER_PROFILE_QUERY, data })
                    }
                })

        }
    },
  }

Adding refetchQueries: [ { query: gql(listComments), variables: { id: xxx-xxx-xxx } } ] Fixed everything for me

For anyone else who may have the same issue, my problem was just that I didn’t include my variables when defining the cache read and write queries. Oops!

FWIW, I got stuck with this error as well and it turned out to be totally unrelated to any of the above. In my case, the original query received the variable I passed it as a Number, whereas the subsequent cache.readQuery was passing the same variable as a String (due to some roundabout data manipulation). Yup, dumb… but figured I’d share since it wasn’t that obvious to discover the bug.

if(cache.data.data.ROOT_QUERY){

thanks, that was the solution for me : I went a little further, checking that my query is in the cache:

if( cache.data.data.ROOT_QUERY.complaintConnection ) { query and update) even though I think this is too much boilerplate, apollo should handle all this querying and adding an item in a specialized method like addItemToQuery(query, path, item), this should avoid all this code

Just in case anyone else does the same thing I did…

Make sure you’ve actually called the query you’re trying to read. I spent an afternoon bashing my head against this error on an edit form. The query I was trying to read was being called on a list page. If I first loaded that list page, and then went to the page to edit a single item on that list, reading the query worked just fine.

Wrapping this in a try/catch, on the off chance someone does get to the edit page without first visiting the list…

I ran into this issue, but then realised the problem with my logic - it is unnecessary to write e.g. a new Post to the cache if I am not trying to query a list of Posts on that current page.

I believe the React-Apollo way would be to e.g. redirect to the /posts/${post.id} page and then fetch the newly made post.

@jonathanphz @acomito, still havn’t found a solution, but follow https://github.com/apollographql/apollo-client/issues/2051 which hopefully will end up with a solution.

There is no cached data to be compared (even if you used @TdyP solution) Two scenarios are happening:

  • first, creating a new list item without opening list screen => causes issue
  • second, before creating new item, open list and then create new item => no issue

This is particularly annoying, since I’m using relay style pagination, and the cursor must always be NULL to start from the beginning.

Example error, actually containing why:

Error: Can't find field memories({"first":50}) on object ($ROOT_QUERY.viewer) {
  "memories({\"first\":50,\"after\":null,\"memory\":null})": {
    "type": "id",
    "id": "$ROOT_QUERY.viewer.memories({\"first\":50,\"after\":null,\"memory\":null})",
    "generated": true
  },
  "__typename": "User"
}.