From 4411bea1765ca660711eaf6374c442b32cf582b9 Mon Sep 17 00:00:00 2001 From: Atharv Pandey Date: Tue, 28 Jul 2026 22:41:50 +0530 Subject: [PATCH 1/4] fix: apply query variable defaults when undefined is passed explicitly When query variables like { show: undefined } are passed, the cache's diffQueryAgainstStore would overwrite GraphQL-specified default values with undefined by spreading the user-provided variables directly. This caused @include/@skip directives to fail with 'Invalid variable referenced' errors. Filter out undefined values before merging with defaults, matching the behavior already implemented in QueryManager.getVariables. Fixes #13345 --- src/cache/inmemory/__tests__/readFromStore.ts | 31 +++++++++++++++++++ src/cache/inmemory/readFromStore.ts | 5 ++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/cache/inmemory/__tests__/readFromStore.ts b/src/cache/inmemory/__tests__/readFromStore.ts index 576950ad100..0fbe32dfaa0 100644 --- a/src/cache/inmemory/__tests__/readFromStore.ts +++ b/src/cache/inmemory/__tests__/readFromStore.ts @@ -262,6 +262,37 @@ describe("reading from the store", () => { }); }); + it("applies defaults when variable is explicitly undefined", () => { + const query = gql` + query someQuery($show: Boolean = false) { + id + field @include(if: $show) + } + `; + + const variables = { + show: undefined, + }; + + const store = defaultNormalizedCacheFactory({ + ROOT_QUERY: { + __typename: "Query", + id: "abcd", + } as StoreObject, + }); + + const result = readQueryFromStore(reader, { + store, + query, + variables, + }); + + // $show defaults to false, so @include(if: false) excludes the field + expect(result).toEqual({ + id: "abcd", + }); + }); + it("runs a nested query", () => { const result: any = { id: "abcd", diff --git a/src/cache/inmemory/readFromStore.ts b/src/cache/inmemory/readFromStore.ts index 1d1bf3ee828..6b70b7ac615 100644 --- a/src/cache/inmemory/readFromStore.ts +++ b/src/cache/inmemory/readFromStore.ts @@ -204,9 +204,12 @@ export class StoreReader { }: DiffQueryAgainstStoreOptions): Cache.DiffResult { const policies = this.config.cache.policies; + const rawVariables = variables ?? {}; variables = { ...getDefaultValues(getQueryDefinition(query)), - ...variables!, + ...Object.fromEntries( + Object.entries(rawVariables).filter(([, v]) => v !== undefined) + ), }; const rootRef = makeReference(rootId); From 60ae9805ce5b22c8a0afcdf3404596bf84049e9d Mon Sep 17 00:00:00 2001 From: Atharv Pandey Date: Wed, 29 Jul 2026 10:08:13 +0530 Subject: [PATCH 2/4] fix: use compact utility, add tests for omitted variables, add changeset --- .changeset/pink-shoes-cheer.md | 5 ++++ src/cache/inmemory/__tests__/readFromStore.ts | 26 ++++++++++++++++++- src/cache/inmemory/readFromStore.ts | 12 ++++----- 3 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 .changeset/pink-shoes-cheer.md diff --git a/.changeset/pink-shoes-cheer.md b/.changeset/pink-shoes-cheer.md new file mode 100644 index 00000000000..f455b462d6b --- /dev/null +++ b/.changeset/pink-shoes-cheer.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +Fix a bug where GraphQL variable default values were not applied when `undefined` was passed explicitly in `variables`. This caused `@include`/`@skip` directives to throw "Invalid variable referenced" errors when the variable was passed as `undefined` instead of being omitted entirely. diff --git a/src/cache/inmemory/__tests__/readFromStore.ts b/src/cache/inmemory/__tests__/readFromStore.ts index 0fbe32dfaa0..84d64eb8862 100644 --- a/src/cache/inmemory/__tests__/readFromStore.ts +++ b/src/cache/inmemory/__tests__/readFromStore.ts @@ -287,7 +287,31 @@ describe("reading from the store", () => { variables, }); - // $show defaults to false, so @include(if: false) excludes the field + expect(result).toEqual({ + id: "abcd", + }); + }); + + it("applies defaults when variables are omitted entirely", () => { + const query = gql` + query someQuery($show: Boolean = false) { + id + field @include(if: $show) + } + `; + + const store = defaultNormalizedCacheFactory({ + ROOT_QUERY: { + __typename: "Query", + id: "abcd", + } as StoreObject, + }); + + const result = readQueryFromStore(reader, { + store, + query, + }); + expect(result).toEqual({ id: "abcd", }); diff --git a/src/cache/inmemory/readFromStore.ts b/src/cache/inmemory/readFromStore.ts index 6b70b7ac615..3a528761d45 100644 --- a/src/cache/inmemory/readFromStore.ts +++ b/src/cache/inmemory/readFromStore.ts @@ -16,6 +16,7 @@ import type { FragmentMapFunction, } from "@apollo/client/utilities/internal"; import { + compact, DeepMerger, getDefaultValues, getFragmentFromSelection, @@ -204,13 +205,10 @@ export class StoreReader { }: DiffQueryAgainstStoreOptions): Cache.DiffResult { const policies = this.config.cache.policies; - const rawVariables = variables ?? {}; - variables = { - ...getDefaultValues(getQueryDefinition(query)), - ...Object.fromEntries( - Object.entries(rawVariables).filter(([, v]) => v !== undefined) - ), - }; + variables = compact( + getDefaultValues(getQueryDefinition(query)), + variables, + ); const rootRef = makeReference(rootId); const execResult = this.executeSelectionSet({ From 96b76ec55eb7eafbd481ed1e1b89b683ed99c433 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Wed, 29 Jul 2026 17:19:52 -0600 Subject: [PATCH 3/4] Update .changeset/pink-shoes-cheer.md --- .changeset/pink-shoes-cheer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pink-shoes-cheer.md b/.changeset/pink-shoes-cheer.md index f455b462d6b..f171a881420 100644 --- a/.changeset/pink-shoes-cheer.md +++ b/.changeset/pink-shoes-cheer.md @@ -2,4 +2,4 @@ "@apollo/client": patch --- -Fix a bug where GraphQL variable default values were not applied when `undefined` was passed explicitly in `variables`. This caused `@include`/`@skip` directives to throw "Invalid variable referenced" errors when the variable was passed as `undefined` instead of being omitted entirely. +Fix a bug where GraphQL variable default values were not applied during cache reads when variables with defaults were explicitly set to `undefined`. This caused `@include`/`@skip` directives to throw "Invalid variable referenced" errors when the variable was passed as `undefined` instead of being omitted entirely. From ecd2e19da1547fc338c443b55ff45edd19aa8ec5 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Wed, 29 Jul 2026 17:20:48 -0600 Subject: [PATCH 4/4] Formatting --- src/cache/inmemory/readFromStore.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/cache/inmemory/readFromStore.ts b/src/cache/inmemory/readFromStore.ts index 3a528761d45..bf657557c48 100644 --- a/src/cache/inmemory/readFromStore.ts +++ b/src/cache/inmemory/readFromStore.ts @@ -205,10 +205,7 @@ export class StoreReader { }: DiffQueryAgainstStoreOptions): Cache.DiffResult { const policies = this.config.cache.policies; - variables = compact( - getDefaultValues(getQueryDefinition(query)), - variables, - ); + variables = compact(getDefaultValues(getQueryDefinition(query)), variables); const rootRef = makeReference(rootId); const execResult = this.executeSelectionSet({