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

Cache is replacing the objects within an array being stored #4025

Closed
MLDimitry opened this issue Oct 18, 2018 · 2 comments
Closed

Cache is replacing the objects within an array being stored #4025

MLDimitry opened this issue Oct 18, 2018 · 2 comments
Assignees

Comments

@MLDimitry
Copy link

MLDimitry commented Oct 18, 2018

I am using Apollo Link State to store an array of "SelectedMessage" objects. Each "SelectedMessage" object should look like:
{ __typename : String!, messageId : String!, labels : [ String ] }

This stored array is fetched and updated any time a message is selected/unselected. It works like a toggle. If the message is already in the array, it should be filtered out. If it's not in the array, it's concat'd in. But that functionality is not working because the "messageId" field I am comparing on does not exist on the object that is returned within the array.

Intended outcome:
When I retrieve my selectedMessageList I expect it to be in the format [ SelectedMessage ].

Actual outcome:
But for some reason, when I retrieve this array using cache.readQuery(), the array being returned is some sort of [ NormalizedSelectedMessageObjectOrSomething ] in the format of:
{ type : String, id : String, generated : Boolean, }

Code and logging are below.

Code

toggleMailMessageIsSelectedResolver mutation on local cache

toggleMailMessageIsSelected : ( _ : any, { messageId, labels } : SelectedMessage, { cache } : ApolloCacheContext ) => {

                const selectedMessageListQueryResult : { selectedMessageList : Array<SelectedMessage> } | null = cache.readQuery( { query : localQueries.getSelectedMessageList } );

                /*
                const cacheResult = cache.readFragment( {
                    id : messageId,
                    fragment : gql`
                        fragment selectedFragment on MailMessage {
                            messageId,
                            labels,
                        }
                    `,
                } );

                LogToConsole( "Cache result here ", cacheResult, true, true );
*/
                if ( selectedMessageListQueryResult ) {
                    const { selectedMessageList } = selectedMessageListQueryResult;

                    // add or remove message ID to array depending on if we're selecting or unselecting

                    LogToConsole( "Selected List from cache", selectedMessageListQueryResult, true, true );

                    const data = {
                        selectedMessageList : selectedMessageList.findIndex( ( selectedMessage : SelectedMessage ) => {
                            return selectedMessage.messageId === messageId;
                        } ) !== -1
                            ? selectedMessageList.filter( ( selectedMessage : ToggleIsSelectedVariables ) => selectedMessage.messageId !== messageId )
                            : selectedMessageList.concat( [ { __typename: "SelectedMessage", messageId : messageId, labels : labels } ] )
                    };

                    LogToConsole( "Data to store", data, true, true );
                    // over-write updated data to cache

                    cache.writeData( { data } );
                    LogToConsole( "Store complete" );
                    return true;
                } else {
                    return false;
                }
            },

Logging

Chrome dev tools logs after clicking the toggle select button 3 times

LOG[15:09:14,111] ==> Selected List from cache JSON: 
{
	"selectedMessageList": []
}
debug.ts:56 LOG[15:09:14,112] ==> Data to store JSON: 
{
	"selectedMessageList": [
		{
			"__typename": "SelectedMessage",
			"messageId": "1668894823eada37",
			"labels": [
				"UNREAD",
				"INBOX"
			]
		}
	]
}
debug.ts:56 LOG[15:09:14,116] ==> Store complete
debug.ts:56 LOG[15:09:18,020] ==> Selected List from cache JSON: 
{
	"selectedMessageList": [
		{
			"type": "id",
			"generated": false,
			"id": "1668894823eada37",
			"typename": "SelectedMessage"
		}
	]
}
debug.ts:56 LOG[15:09:18,021] ==> Data to store JSON: 
{
	"selectedMessageList": [
		{
			"type": "id",
			"generated": false,
			"id": "1668894823eada37",
			"typename": "SelectedMessage"
		},
		{
			"__typename": "SelectedMessage",
			"messageId": "1668894823eada37",
			"labels": [
				"UNREAD",
				"INBOX"
			]
		}
	]
}
writeToStore.js:137 Missing field __typename in {
  "type": "id",
  "generated": false,
  "id": "1668894823eada37",
  "typename": "SelectedMessage"

writeToStore.js:137 Missing field type in {
  "__typename": "SelectedMessage",
  "messageId": "1668894823eada37",
  "labels": [
    "UNREAD",

writeToStore.js:137 Missing field id in {
  "__typename": "SelectedMessage",
  "messageId": "1668894823eada37",
  "labels": [
    "UNREAD",

debug.ts:56 LOG[15:09:18,028] ==> Store complete
debug.ts:56 LOG[15:09:21,187] ==> Selected List from cache JSON: 
{
	"selectedMessageList": [
		{
			"type": "id",
			"generated": true,
			"id": "ROOT_QUERY.selectedMessageList.0"
		},
		{
			"type": "id",
			"generated": false,
			"id": "1668894823eada37",
			"typename": "SelectedMessage"
		}
	]
}
debug.ts:56 LOG[15:09:21,188] ==> Data to store JSON: 
{
	"selectedMessageList": [
		{
			"type": "id",
			"generated": true,
			"id": "ROOT_QUERY.selectedMessageList.0"
		},
		{
			"type": "id",
			"generated": false,
			"id": "1668894823eada37",
			"typename": "SelectedMessage"
		},
		{
			"__typename": "SelectedMessage",
			"messageId": "1668894823eada37",
			"labels": [
				"UNREAD",
				"INBOX"
			]
		}
	]
}
writeToStore.js:137 Missing field typename in {
  "type": "id",
  "generated": true,
  "id": "ROOT_QUERY.selectedMessageList.0"
}
writeToStore.js:137 Missing field __typename in {
  "type": "id",
  "generated": true,
  "id": "ROOT_QUERY.selectedMessageList.0"
}
writeToStore.js:137 Missing field __typename in {
  "type": "id",
  "generated": false,
  "id": "1668894823eada37",
  "typename": "SelectedMessage"

writeToStore.js:137 Missing field type in {
  "__typename": "SelectedMessage",
  "messageId": "1668894823eada37",
  "labels": [
    "UNREAD",

writeToStore.js:137 Missing field generated in {
  "__typename": "SelectedMessage",
  "messageId": "1668894823eada37",
  "labels": [
    "UNREAD",

writeToStore.js:137 Missing field typename in {
  "__typename": "SelectedMessage",
  "messageId": "1668894823eada37",
  "labels": [
    "UNREAD",

debug.ts:56 LOG[15:09:21,197] ==> Store complete

How to reproduce the issue:
This Code Sandbox project reproduces the issue:
https://codesandbox.io/s/lpxqyv0z4z

Versions
System:
OS: macOS High Sierra 10.13.6
Binaries:
Node: 10.11.0 - /usr/local/bin/node
Yarn: yarn install v0.23.2
[1/4] Resolving packages...
success Already up-to-date.
Done in 0.52s. - /usr/local/bin/yarn
npm: 6.4.1 - /usr/local/bin/npm
Browsers:
Chrome: 69.0.3497.100
Firefox: 61.0.1
Safari: 12.0
npmPackages:
apollo-boost: ^0.1.13 => 0.1.16
react-apollo: ^2.1.11 => 2.2.4

benjamn added a commit that referenced this issue Oct 23, 2018
This would have helped diagnose the cause of #4025, which is that query
fields with non-scalar types (or list-of-non-scalar types) must have a
selection set.

In other words, given a schema like

  type Query {
    selectedMessageList: [SelectedMessage]
  }

a query like

  query getSelectedMessageList {
    selectedMessageList @client
  }

is invalid since it doesn't specify which fields of the SelectedMessage
objects should be included in the list.
@benjamn benjamn self-assigned this Oct 23, 2018
@benjamn
Copy link
Member

benjamn commented Oct 23, 2018

@MLDimitry The root of the problem seems to be that your query

query getSelectedMessageList {
  selectedMessageList @client
}

needs to have a selection set after the selectedMessageList field, since the result is a list of non-scalar values. Without a selection set, there's no way to know which fields of the SelectedMessage objects should be included in the result items.

However, it's definitely a bug that the cache did not throw an appropriate error, and instead exposed the internal IdValue objects that we use to represent normalized object references!

I've submitted PR #4038 to make sure we throw a more helpful error.

benjamn added a commit that referenced this issue Oct 24, 2018
This would have helped diagnose the cause of #4025, which is that query
fields with non-scalar types (or list-of-non-scalar types) must have a
selection set.

In other words, given a schema like

  type Query {
    selectedMessageList: [SelectedMessage]
  }

a query like

  query getSelectedMessageList {
    selectedMessageList @client
  }

is invalid since it doesn't specify which fields of the SelectedMessage
objects should be included in the list.
benjamn added a commit that referenced this issue Oct 26, 2018
This would have helped diagnose the cause of #4025, which is that query
fields with non-scalar types (or list-of-non-scalar types) must have a
selection set.

In other words, given a schema like

  type Query {
    selectedMessageList: [SelectedMessage]
  }

a query like

  query getSelectedMessageList {
    selectedMessageList @client
  }

is invalid since it doesn't specify which fields of the SelectedMessage
objects should be included in the list.
@benjamn
Copy link
Member

benjamn commented Oct 26, 2018

Closing because I believe apollo-cache-inmemory@1.3.7 is now capable of detecting and warning about the problem I described above. Thanks for reporting this issue @MLDimitry!

@benjamn benjamn closed this as completed Oct 26, 2018
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 16, 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

2 participants