Skip to content

More robust handling of local state default value when read function is defined#12934

Merged
jerelmiller merged 26 commits intorelease-4.1from
jerel/local-state-bug
Sep 26, 2025
Merged

More robust handling of local state default value when read function is defined#12934
jerelmiller merged 26 commits intorelease-4.1from
jerel/local-state-bug

Conversation

@jerelmiller
Copy link
Copy Markdown
Member

@jerelmiller jerelmiller commented Sep 22, 2025

Closes #12930.

Better detect whether the cache can handle resolving a client field and warn if neither a local resolver or read function is defined. If a read function is defined, don't set a default value of null for the field to allow for the read function to set the value. This allows default parameters to work again.

This also adds a check for no-cache fetch policies and will warn if used with a @client field since the cache is not read. This fixes an issue where using a no-cache fetch policy would read from the cache to get the cache diff to resolve the field.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Sep 22, 2025

🦋 Changeset detected

Latest commit: 4bceff8

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Sep 22, 2025

npm i https://pkg.pr.new/apollographql/apollo-client/@apollo/client@12934

commit: 4bceff8

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Sep 22, 2025

size-limit report 📦

Path Size
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client" (CJS) 43.7 KB (+0.48% 🔺)
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client" (production) (CJS) 38.5 KB (-0.11% 🔽)
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client" 33.11 KB (+0.61% 🔺)
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client" (production) 27.08 KB (+0.08% 🔺)
import { ApolloProvider } from "@apollo/client/react" 6.11 KB (+2.31% 🔺)
import { ApolloProvider } from "@apollo/client/react" (production) 1001 B (0%)
import { useQuery } from "@apollo/client/react" 7.54 KB (+1.54% 🔺)
import { useQuery } from "@apollo/client/react" (production) 2.39 KB (0%)
import { useLazyQuery } from "@apollo/client/react" 7.29 KB (+1.88% 🔺)
import { useLazyQuery } from "@apollo/client/react" (production) 2.16 KB (0%)
import { useMutation } from "@apollo/client/react" 6.66 KB (+1.98% 🔺)
import { useMutation } from "@apollo/client/react" (production) 1.52 KB (0%)
import { useSubscription } from "@apollo/client/react" 6.99 KB (+2.19% 🔺)
import { useSubscription } from "@apollo/client/react" (production) 1.82 KB (0%)
import { useSuspenseQuery } from "@apollo/client/react" 8.77 KB (+1.51% 🔺)
import { useSuspenseQuery } from "@apollo/client/react" (production) 3.67 KB (0%)
import { useBackgroundQuery } from "@apollo/client/react" 8.57 KB (+1.85% 🔺)
import { useBackgroundQuery } from "@apollo/client/react" (production) 3.44 KB (0%)
import { useLoadableQuery } from "@apollo/client/react" 8.51 KB (+1.56% 🔺)
import { useLoadableQuery } from "@apollo/client/react" (production) 3.43 KB (0%)
import { useReadQuery } from "@apollo/client/react" 6.78 KB (+1.93% 🔺)
import { useReadQuery } from "@apollo/client/react" (production) 1.66 KB (0%)
import { useFragment } from "@apollo/client/react" 6.84 KB (+1.97% 🔺)
import { useFragment } from "@apollo/client/react" (production) 1.71 KB (0%)

@apollo-librarian
Copy link
Copy Markdown

apollo-librarian bot commented Sep 22, 2025

✅ Docs preview has no changes

The preview was not built because there were no changes.

Build ID: 7e3a261fa5d255f518c30676
Build Logs: View logs

});

expect(merge).toHaveBeenCalledTimes(1);
expect(merge).toHaveBeenCalledWith(undefined, null, expect.anything());
Copy link
Copy Markdown
Member Author

@jerelmiller jerelmiller Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a merge function is present, we'll call it with null as the incoming value which I think makes sense since its resolved from LocalState. When a read function is the only thing defined, the merge function won't be called at all because the incoming value is set to undefined instead and merge functions aren't called in this case.

@jerelmiller jerelmiller changed the title [WIP] Alternate solution to local state value with read function Alternate solution to local state value with read function Sep 23, 2025
@jerelmiller
Copy link
Copy Markdown
Member Author

@phryneas would love your initial input on this as a potential solution for the issue. I'll open this up and get ready for merge if you like the approach.

@jerelmiller jerelmiller changed the title Alternate solution to local state value with read function More robust handling of local state default value when read function is defined Sep 23, 2025
@jerelmiller jerelmiller marked this pull request as ready for review September 23, 2025 21:45
@@ -0,0 +1,7 @@
---
"@apollo/client": minor
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I marked this as minor (in which case we'll want to repoint the branch) since it feels like enough of a change to justify a minor. Happy to update to patch though if it feels more like a bug fix. Opinions welcome.

@@ -0,0 +1,7 @@
---
"@apollo/client": minor
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same. Would like opinions on whether this should be viewed as a minor or patch.

"@apollo/client": patch
---

Warn when using a `no-cache` fetch policy when a `@client` field resolves the value from the cache that defines a `read` function.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you phrase this differently? Without context, this one might be very difficult to understand.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeesh this was horrible 😅. Updated in 0ff610d

"The '%s' field resolves the value from the cache, for example from a 'read' function, but a 'no-cache' fetch policy was used. The field value has been set to `null`. Either define a local resolver or use a fetch policy that uses the cache to ensure the field is resolved correctly.",
resolverName
);
return null;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to return null here even in the returnPartialData: true case?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I initially thought yet, but I've changed my mind only because there is no other way to resolve the field value. no-cache and returnPartialData are technically incompatible together since no-cache queries don't read from the cache, so there isn't a possibility of a partial result. The same applies here since the value can only be resolved in LocalState when a no-cache query is issued since the read function isn't called. In this case, I think its better to have a null to ensure the query is complete since it can't be resolved any other way.

}

// assume the cache will handle returning the correct value
returnPartialData = true;
Copy link
Copy Markdown
Member

@phryneas phryneas Sep 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing this here will disable the warning and null-returning behaviour a few lines down for all future fields. Do we want that to happen?
Might be necessary to create an immutable copy of the initial returnPartialData otherwise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed in person. This is caught by the conditional below that returns early when the value is null or undefined.

@jerelmiller jerelmiller changed the base branch from main to release-4.1 September 26, 2025 05:14
Copy link
Copy Markdown
Member

@phryneas phryneas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good now :)

@github-actions github-actions bot added the auto-cleanup 🤖 label Sep 26, 2025
@jerelmiller jerelmiller merged commit 54ab6d9 into release-4.1 Sep 26, 2025
46 checks passed
@jerelmiller jerelmiller deleted the jerel/local-state-bug branch September 26, 2025 14:57
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 27, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect resolution of nested @client fields in v4

2 participants