-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Add section in docs for child read functions with @client fields
#12931
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| { | ||
| "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (CJS)": 43857, | ||
| "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (production) (CJS)": 38699, | ||
| "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\"": 33415, | ||
| "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (production)": 27498 | ||
| "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (CJS)": 43872, | ||
| "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (production) (CJS)": 38703, | ||
| "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\"": 33436, | ||
| "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (production)": 27453 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -84,6 +84,61 @@ | |
|
|
||
| > If you query a local-only field that _doesn't_ define a `read` function, Apollo Client performs a default cache lookup for the field. See [Storing local state in the cache](#storing-local-state-in-the-cache) for details. | ||
|
|
||
| <Caution> | ||
|
|
||
| The initial `existing` value provided to the `read` function is `null` instead of `undefined` when the `@client` field is a child of another server field. This means using default parameters to set a default value doesn't work as expected and instead returns `null` as the value for the field. Note that this behavior doesn't affect root `@client` fields which receive `undefined` as the initial `existing` value.<br/><br/> | ||
|
Check warning on line 89 in docs/source/local-state/managing-state-with-field-policies.mdx
|
||
|
|
||
| <ExpansionPanel title="Understanding this behavior"> | ||
|
|
||
| When you're using a `fetchPolicy` that reads from the cache, Apollo Client first tries to fulfill the query from the cache before executing the query on the network. In the context of `@client` fields, `LocalState` is treated as part of the network layer when resolving field values.<br/><br/> | ||
|
|
||
| `LocalState` fulfills values for `@client` fields by first running local resolvers. If a local resolver isn't provided for a particular `@client` field, `LocalState` then tries to resolve the value from the cache. If the cache doesn't contain a value for the field, the field value is set to `null` to ensure future cache reads don't result in a cache miss.<br/><br/> | ||
|
|
||
| When using `read` functions to resolve root `@client` fields, those `read` functions run as a result of executing the initial cache read. If a value hasn't yet been written to the cache, the `existing` value is set to `undefined`, allowing default parameters to provide a default value for the field.<br/><br/> | ||
|
|
||
| When using `read` functions to resolve child `@client` fields of other server fields, `read` functions might not be run as part of the initial cache read when the parent field doesn't return a cached value. The query is then executed on the network, which includes running the query through `LocalState` to fulfill data for `@client` fields. In this scenario, `LocalState` resolves the value of the `@client` field to `null` before the `read` function is run. Unless your `merge` function alters the `null` value before it's written to the cache, the `existing` value provided to the `read` function is `null`. | ||
|
Check warning on line 99 in docs/source/local-state/managing-state-with-field-policies.mdx
|
||
|
|
||
| </ExpansionPanel> | ||
|
|
||
| To provide default values for `read` functions on `@client` fields, use the nullish coalescing operator: | ||
|
|
||
| ```ts | ||
| new InMemoryCache({ | ||
| typePolicies: { | ||
| Product: { | ||
| fields: { | ||
| isInCart: { | ||
| // ❌ Don't use default parameter values, otherwise the | ||
| // value will be null | ||
| read(value = false) { | ||
| return value; | ||
| }, | ||
|
|
||
| // ✅ Use nullish coalescing operator to set a default value | ||
| read(value) { | ||
| return value ?? false; | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
|
|
||
| Query: { | ||
| fields: { | ||
| rootClientField: { | ||
| // ✅ Default parameters on root client field | ||
| // read functions work as expected | ||
| read(value = "rootFieldDefault") { | ||
| return value; | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| </Caution> | ||
|
|
||
| ### Reads are synchronous by design | ||
|
|
||
| Many UI frameworks like React (when not using `Suspense`) have synchronous rendering pipelines, therefore it's important for UI components to have immediate access to any existing data. This is why all `read` functions are synchronous, as are the cache's `readQuery` and `readFragment` methods. It is possible, however, to leverage reactive variables and `options.storage` to compose a `read` function that behaves in a manner that resembles an asynchronous action: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this something we can help with by providing a dev-only warning?
It's a bit tricky, but this should catch most cases:
lengthis defined as the number or arguments before the first optional argument, solength == 0means either spread arguments, no arguments or default argumentsUnicodeEscapeSequence,[\p{ID_Start}$_][\p{ID_Continue}$]*should match all of themI think this should eliminate all weird one-offs, but an additional check for a general function shape of the string might make it even more solid so it doesn't warn on engine builtins (but why would one pass those in there?)