diff --git a/docs/source/api/cache/InMemoryCache.mdx b/docs/source/api/cache/InMemoryCache.mdx index c439181d308..1e488d927dd 100644 --- a/docs/source/api/cache/InMemoryCache.mdx +++ b/docs/source/api/cache/InMemoryCache.mdx @@ -146,7 +146,7 @@ readQuery<QueryType, TVariables = any>( Writes data to the cache in the shape of a provided GraphQL query. Returns a `Reference` to the written object or `undefined` if the write failed. -For usage instructions, see [Interacting with cached data: `writeQuery`](../../caching/cache-interaction/#writequery-and-writefragment). +For usage instructions, see [Interacting with cached data: `writeQuery`](../../caching/cache-interaction/#writequery). Takes an `options` object as a parameter. Supported fields of this object are described below. @@ -398,7 +398,7 @@ readFragment<FragmentType, TVariables = any>( Writes data to the cache in the shape of the provided GraphQL fragment. Returns a `Reference` to the written object or `undefined` if the write failed. -For usage instructions, see [Interacting with cached data: `writeFragment`](../../caching/cache-interaction/#writequery-and-writefragment). +For usage instructions, see [Interacting with cached data: `writeFragment`](../../caching/cache-interaction/#writefragment). Takes an `options` object as a parameter. Supported fields of this object are described below. @@ -567,7 +567,7 @@ Modifies one or more field values of a cached object. Must provide a **modifier Returns `true` if the cache was modified successfully and `false` otherwise. -For usage instructions, see [`cache.modify`](../../caching/cache-interaction/#cachemodify). +For usage instructions, see [Using `cache.modify`](../../caching/cache-interaction/#using-cachemodify). Takes an `options` object as a parameter. Supported fields of this object are described below. diff --git a/docs/source/api/react/hoc.mdx b/docs/source/api/react/hoc.mdx index ac866d19298..667e9fb9e69 100644 --- a/docs/source/api/react/hoc.mdx +++ b/docs/source/api/react/hoc.mdx @@ -902,7 +902,7 @@ This option allows you to update your store based on your mutationโs result. B `options.update` takes two arguments. The first is an instance of a `DataProxy` object which has some methods which will allow you to interact with the data in your store. The second is the response from your mutation - either the optimistic response, or the actual response returned by your server (see the mutation result described in the [mutation render prop](./components/#render-prop-function-1) section for more details). -In order to change the data in your store call methods on your `DataProxy` instance like [`writeQuery` and `writeFragment`](../../caching/cache-interaction/#writequery-and-writefragment). This will update your cache and reactively re-render any of your GraphQL components which are querying affected data. +In order to change the data in your store call methods on your `DataProxy` instance like [`writeQuery`](../../caching/cache-interaction/#writequery) and [`writeFragment`](../../caching/cache-interaction/#writefragment). This will update your cache and reactively re-render any of your GraphQL components which are querying affected data. To read the data from the store that you are changing, make sure to use methods on your `DataProxy` like [`readQuery`](../../caching/cache-interaction/#readquery) and [`readFragment`](../../caching/cache-interaction/#readfragment). diff --git a/docs/source/caching/cache-configuration.md b/docs/source/caching/cache-configuration.mdx similarity index 82% rename from docs/source/caching/cache-configuration.md rename to docs/source/caching/cache-configuration.mdx index 655189e6761..b334513693b 100644 --- a/docs/source/caching/cache-configuration.md +++ b/docs/source/caching/cache-configuration.mdx @@ -3,6 +3,8 @@ title: Configuring the cache sidebar_title: Configuration --- +import {ExpansionPanel} from 'gatsby-theme-apollo-docs'; + Apollo Client stores the results of its GraphQL queries in a normalized, in-memory cache. This enables your client to respond to future queries for the same data without sending unnecessary network requests. This article describes cache setup and configuration. To learn how to interact with cached data, see [Reading and writing data to the cache](./cache-interaction). @@ -54,12 +56,14 @@ To customize cache behavior, provide an `options` object to the `InMemoryCache` ###### `addTypename` `Boolean` + </td> <td> If `true`, the cache automatically adds `__typename` fields to all outgoing queries, removing the need to add them manually. The default value is `true`. + </td> </tr> @@ -69,12 +73,14 @@ The default value is `true`. ###### `resultCaching` `Boolean` + </td> <td> If `true`, the cache returns an identical (`===`) response object for every execution of the same query, as long as the underlying data remains unchanged. This makes it easier to detect changes to a query's result. The default value is `true`. + </td> </tr> @@ -84,6 +90,7 @@ The default value is `true`. ###### `possibleTypes` `Object` + </td> <td> @@ -92,6 +99,7 @@ Include this object to define polymorphic relationships between your schema's ty Each key in the object is the `__typename` of an interface or union, and the corresponding value is an array of the `__typename`s of the types that belong to that union or implement that interface. For an example, see [Defining `possibleTypes` manually](../data/fragments/#defining-possibletypes-manually). + </td> </tr> @@ -101,12 +109,14 @@ For an example, see [Defining `possibleTypes` manually](../data/fragments/#defin ###### `typePolicies` `Object` + </td> <td> Include this object to customize the cache's behavior on a type-by-type basis. Each key in the object is the `__typename` of a type to customize, and the corresponding value is a [`TypePolicy` object](#typepolicy-fields). + </td> </tr> @@ -116,12 +126,14 @@ Each key in the object is the `__typename` of a type to customize, and the corre ###### `dataIdFromObject` `Function` + </td> <td> **Deprecated.** A function that takes a response object and returns a unique identifier to be used when normalizing the data in the store. Deprecated in favor of the `keyFields` option of the [`TypePolicy` object](#typepolicy-fields). + </td> </tr> </tbody> @@ -234,6 +246,91 @@ To disable normalization for a type, define a `TypePolicy` for the type (as show Objects that are not normalized are instead embedded within their _parent_ object in the cache. You can't access these objects directly, but you can access them via their parent. +## Visualizing the cache + +To help understand the structure of your cached data, we strongly recommend installing the [Apollo Client Devtools](../development-testing/developer-tooling/#apollo-client-devtools). + +This browser extension includes an inspector that enables you to view all of the normalized objects contained in your cache: + +<img src="../img/cache-inspector.jpg" class="screenshot" alt="The Cache tab of the Apollo Client Devtools"></img> + +### Example + +Let's say we use Apollo Client to run the following query on the [SWAPI demo API](https://github.com/graphql/swapi-graphql): + +```graphql +query { + allPeople(first:3) { # Return the first 3 items + people { + id + name + homeworld { + id + name + } + } + } +} +``` + +This query returns the following result of three `Person` objects, each with a corresponding `homeworld` (a `Planet` object): + +<ExpansionPanel title="Click to expand"> + +```json +{ + "data": { + "allPeople": { + "people": [ + { + "__typename": "Person", + "id": "cGVvcGxlOjE=", + "name": "Luke Skywalker", + "homeworld": { + "__typename": "Planet", + "id": "cGxhbmV0czox", + "name": "Tatooine" + } + }, + { + "__typename": "Person", + "id": "cGVvcGxlOjI=", + "name": "C-3PO", + "homeworld": { + "__typename": "Planet", + "id": "cGxhbmV0czox", + "name": "Tatooine" + } + }, + { + "__typename": "Person", + "id": "cGVvcGxlOjM=", + "name": "R2-D2", + "homeworld": { + "__typename": "Planet", + "id": "cGxhbmV0czo4", + "name": "Naboo" + } + } + ] + } + } +} +``` + +</ExpansionPanel> + +> Notice that each object in the result includes a `__typename` field, even though our query string _didn't_ include this field. That's because Apollo Client _automatically_ queries for every object's `__typename` field. + +After the result is cached, we can view the state of our cache in the Apollo Client Devtools: + +<img src="../img/cache-inspector.jpg" class="screenshot" alt="The Cache tab of the Apollo Client Devtools"></img> + +Our cache now contains five normalized objects (in addition to the `ROOT_QUERY` object): three `Person` objects and two `Planet` objects. + +**Why do we only have two `Planet` objects?** Because two of the three returned `Person` objects have the same `homeworld`. By [normalizing data](#data-normalization) like this, Apollo Client can cache a single copy of an object, and multiple _other_ objects can include _references_ to it (see the `__ref` field of the object in the screenshot above). + + ## `TypePolicy` fields To customize how the cache interacts with specific types in your schema, you can provide an object mapping `__typename` strings to `TypePolicy` objects when you create a new `InMemoryCache` object. diff --git a/docs/source/caching/cache-interaction.md b/docs/source/caching/cache-interaction.md index ce27036cc9c..c49ffff1a51 100644 --- a/docs/source/caching/cache-interaction.md +++ b/docs/source/caching/cache-interaction.md @@ -3,70 +3,147 @@ title: Reading and writing data to the cache sidebar_title: Reading and writing --- -Apollo Client provides the following methods for reading and writing data to the cache: +You can read and write data directly to the Apollo Client cache, _without_ communicating with your GraphQL server. You can interact with data that you previously fetched from your server, _and_ with data that's only available [locally](../local-state/local-state-management/). -* [`readQuery`](#readquery) and [`readFragment`](#readfragment) -* [`writeQuery` and `writeFragment`](#writequery-and-writefragment) -* [`cache.modify`](#cachemodify) (a method of `InMemoryCache`) +Apollo Client supports multiple strategies for interacting with cached data: -These methods are described in detail below. +| Strategy | API | Description | +|----------|-----|-------------| +| [Using GraphQL queries](#using-graphql-queries) | `readQuery` / `writeQuery` | Enables you to use standard GraphQL queries for managing both remote and local data. | +| [Using GraphQL fragments](#using-graphql-fragments) | `readFragment` / `writeFragment` | Enables you to access the fields of any cached object without composing an entire query to reach that object. | +| [Directly modifying cached fields](#using-cachemodify) | `cache.modify` | Enables you to manipulate cached data without using GraphQL at all. | -All code samples below assume that you have initialized an instance of `ApolloClient` and that you have imported the `gql` function from `@apollo/client`. + You can use whichever combination of strategies and methods are most helpful for your use case. -> In a React component, you can access your instance of `ApolloClient` using [`ApolloProvider`](https://www.apollographql.com/docs/react/api/react/hooks/#the-apolloprovider-component) and the [`useApolloClient`](https://www.apollographql.com/docs/react/api/react/hooks/#useapolloclient) hook. +> All code samples below assume that you have initialized an instance of `ApolloClient` and that you have imported the `gql` function from `@apollo/client`. If you haven't, [get started](../get-started). +> +>In a React component, you can access your instance of `ApolloClient` using [`ApolloProvider`](https://www.apollographql.com/docs/react/api/react/hooks/#the-apolloprovider-component) and the [`useApolloClient`](https://www.apollographql.com/docs/react/api/react/hooks/#useapolloclient) hook. -## `readQuery` +## Using GraphQL queries -The `readQuery` method enables you to run a GraphQL query directly on your cache. +You can read and write cache data using GraphQL queries that are similar (or even identical) to queries that you execute on your server: -* If your cache contains all of the data necessary to fulfill the specified query, `readQuery` returns a data object in the shape of that query, just like a GraphQL server does. +### `readQuery` -* If your cache does not contain all of the data necessary to fulfill the specified query, `readQuery` returns `null`, without attempting to fetch data from a remote server. +The `readQuery` method enables you to execute a GraphQL query directly on your cache, like so: -> Prior to Apollo Client 3.3, `readQuery` would throw `MissingFieldError` exceptions to report missing fields. Beginning with Apollo Client 3.3, `readQuery` always returns `null` to indicate fields were missing. - -Pass `readQuery` a GraphQL query string like so: +```js{12-17} +const READ_TODO = gql` + query ReadTodo($id: ID!) { + todo(id: $id) { + id + text + completed + } + } +`; -```js +// Fetch the cached to-do item with ID 5 const { todo } = client.readQuery({ - query: gql` - query ReadTodo { - todo(id: 5) { - id - text - completed - } - } - `, + query: READ_TODO, + variables: { // Provide any required variables here + id: 5, + }, }); ``` -You can provide GraphQL variables to `readQuery` like so: +If your cache contains data for _all_ of the query's fields, `readQuery` returns an object that matches the shape of the query: ```js -const { todo } = client.readQuery({ +{ + todo: { + __typename: 'Todo', // __typename is automatically included + id: 5, + text: 'Buy oranges ๐', + completed: true + } +} +``` + +> Apollo Client automatically queries for every object's `__typename`, even if you don't include this field in your query string. + +**Do not modify the returned object directly.** The same object might be returned to multiple components. To update data in the cache, instead create a replacement object and pass it to [`writeQuery`](#writequery). + +If the cache is missing data for _any_ of the query's fields, `readQuery` returns `null`. It does _not_ attempt to fetch data from your GraphQL server. + +The query you provide `readQuery` can include fields that are _not_ defined in your GraphQL server's schema (i.e., [local-only fields](../local-state/managing-state-with-field-policies/)). + +> Prior to Apollo Client 3.3, `readQuery` threw a `MissingFieldError` exception to report missing fields. Beginning with Apollo Client 3.3, `readQuery` always returns `null` to indicate that fields are missing. + +### `writeQuery` + +The `writeQuery` method enables you to write data to your cache in a shape that matches a GraphQL query. It resembles `readQuery`, except that it requires a `data` option: + +```js +client.writeQuery({ query: gql` - query ReadTodo($id: Int!) { + query WriteTodo($id: Int!) { todo(id: $id) { id text completed } - } - `, - variables: { - id: 5, + }`, + data: { // Contains the data to write + todo: { + __typename: 'Todo', + id: 5, + text: 'Buy grapes ๐', + completed: false + }, }, + variables: { + id: 5 + } }); ``` -> **Do not modify the return value of `readQuery`.** The same object might be returned to multiple components. To update data in the cache, instead create a replacement object and pass it to [`writeQuery`](#writequery-and-writefragment). +This example creates (or edits) a cached `Todo` object with ID `5`. + +Note the following about `writeQuery`: + +* Any changes you make to cached data with `writeQuery` are **not** pushed to your GraphQL server. If you reload your environment, these changes disappear. +* The shape of your query is _not_ enforced by your GraphQL server's schema: + * The query can include fields that are _not_ present in your schema. + * You can (but usually shouldn't) provide values for schema fields that are _invalid_ according to your schema. + +#### Editing existing data + +In the example above, if your cache _already_ contains a `Todo` object with ID `5`, `writeQuery` overwrites the fields that are included in `data` (other fields are preserved): + +```js{6-7,17-18} +// BEFORE +{ + 'Todo:5': { + __typename: 'Todo', + id: 5, + text: 'Buy oranges ๐', + completed: true, + dueDate: '2022-07-02' + } +} + +// AFTER +{ + 'Todo:5': { + __typename: 'Todo', + id: 5, + text: 'Buy grapes ๐', + completed: false, + dueDate: '2022-07-02' + } +} +``` + +> If you include a field in `query` but don't include a _value_ for it in `data`, the field's current cached value is preserved. + +## Using GraphQL fragments -## `readFragment` +You can read and write cache data using GraphQL fragments on _any_ normalized cache object. This provides more "random access" to your cached data than `readQuery`/`writeQuery`, which require a complete valid query. -The `readFragment` method enables you to read data from _any_ normalized cache object that was stored as part of _any_ query result. Unlike with `readQuery`, calls to `readFragment` do not need to conform to the structure of one of your data graph's supported queries. +### `readFragment` -Here's an example that fetches a particular item from a to-do list: +This example fetches the same data as [the example for `readQuery`](#readquery) using `readFragment` instead: ```js const todo = client.readFragment({ @@ -81,13 +158,13 @@ const todo = client.readFragment({ }); ``` -The first argument, `id`, is the value of the unique identifier for the object you want to read from the cache. By default, this is the value of the object's `id` field, but you can [customize this behavior](./cache-configuration/#generating-unique-identifiers). +Unlike `readQuery`, `readFragment` requires an `id` option. This option specifies the unique identifier for the object in your cache. [By default](cache-configuration/#default-identifier-generation), this identifier has the format `<_typename>:<id>` (which is why we provide `Todo:5` above). You can [customize this identifier](./cache-configuration/#customizing-identifier-generation-by-type). -In the example above, `readFragment` returns `null` if no `Todo` object with an `id` of `5` exists in the cache, or if the object exists but is missing the `text` or `completed` fields. +In the example above, `readFragment` returns `null` if no `Todo` object with ID `5` exists in the cache, or if the object exists but is missing a value for either `text` or `completed`. -> Prior to Apollo Client 3.3, `readFragment` would throw `MissingFieldError` exceptions to report missing fields, and return `null` only when reading a fragment from a nonexistent ID. Beginning with Apollo Client 3.3, `readFragment` always returns `null` to indicate insufficient data (missing ID or missing fields), instead of throwing a `MissingFieldError`. +> Prior to Apollo Client 3.3, `readFragment` threw `MissingFieldError` exceptions to report missing fields, and returned `null` only when reading a fragment from a nonexistent ID. Beginning with Apollo Client 3.3, `readFragment` always returns `null` to indicate insufficient data (missing ID or missing fields), instead of throwing a `MissingFieldError`. -## `writeQuery` and `writeFragment` +### `writeFragment` In addition to reading arbitrary data from the Apollo Client cache, you can _write_ arbitrary data to the cache with the `writeQuery` and `writeFragment` methods. @@ -115,7 +192,7 @@ All subscribers to the Apollo Client cache (including all active queries) see th ## Combining reads and writes -Combine `readQuery` and `writeQuery` to fetch currently cached data and make selective modifications to it. The example below creates a new `Todo` item and adds it to your cached to-do list. Remember, this addition is _not_ sent to your remote server. +You can combine `readQuery` and `writeQuery` (or `readFragment` and `writeFragment`) to fetch currently cached data and make selective modifications to it. The example below creates a new `Todo` item and adds it to your cached to-do list. Remember, this addition is _not_ sent to your remote server. ```js // Query that fetches all existing to-do items @@ -149,12 +226,14 @@ client.writeQuery({ }); ``` -## `cache.modify` +## Using `cache.modify` The `modify` method of `InMemoryCache` enables you to directly modify the values of individual cached fields, or even delete fields entirely. -* Like `writeQuery` and `writeFragment`, `modify` triggers a refresh of all active queries that depend on modified fields (unless you override this behavior). -* _Unlike_ `writeQuery` and `writeFragment`, `modify` circumvents any [`merge` functions](cache-field-behavior/#the-merge-function) you've defined, which means that fields are always overwritten with exactly the values you specify. +* Like `writeQuery` and `writeFragment`, `modify` triggers a refresh of all active queries that depend on modified fields (unless you override this behavior by passing `broadcast: false`). +* _Unlike_ `writeQuery` and `writeFragment`: + * `modify` circumvents any [`merge` functions](cache-field-behavior/#the-merge-function) you've defined, which means that fields are always overwritten with exactly the values you specify. + * `modify` _cannot_ write fields that do not already exist in the cache. * Watched queries can control what happens when they are invalidated by updates to the cache, by passing options like `fetchPolicy` and `nextFetchPolicy` to [`client.watchQuery`](../api/core/ApolloClient/#ApolloClient.watchQuery) or the [`useQuery`](../api/react/hooks/#options) hook. ### Parameters @@ -371,7 +450,7 @@ const invisibleManBook = { }; ``` -If we want to interact with this object in our cache with methods like [`writeFragment`](#writequery-and-writefragment) or [`cache.modify`](#cachemodify), we need the object's identifier. Our `Book` type's identifier appears to be custom, because the `id` field isn't present. +If we want to interact with this object in our cache with methods like [`writeFragment`](#writefragment) or [`cache.modify`](#using-cachemodify), we need the object's identifier. Our `Book` type's identifier appears to be custom, because the `id` field isn't present. Instead of needing to look up that our `Book` type uses the `isbn` field as its identifier, we can use the `cache.identify` method, like so: diff --git a/docs/source/img/cache-inspector.jpg b/docs/source/img/cache-inspector.jpg new file mode 100644 index 00000000000..806f2c92b42 Binary files /dev/null and b/docs/source/img/cache-inspector.jpg differ diff --git a/docs/source/local-state/managing-state-with-field-policies.mdx b/docs/source/local-state/managing-state-with-field-policies.mdx index 7951a8b9f26..68428a8b3b2 100644 --- a/docs/source/local-state/managing-state-with-field-policies.mdx +++ b/docs/source/local-state/managing-state-with-field-policies.mdx @@ -228,7 +228,7 @@ As in the earlier `useQuery` example, whenever the `cartItemsVar` variable is up Storing local state directly in the Apollo Client cache provides some advantages, but usually requires more code than [using reactive variables](#storing-local-state-in-reactive-variables): * You don't _have_ to [define a field policy](#defining) for local-only fields that are present in the cache. If you query a field that doesn't define a `read` function, by default Apollo Client attempts to fetch that field's value directly from the cache. -* When you modify a cached field with [`writeQuery` or `writeFragment`](../caching/cache-interaction#writequery-and-writefragment), **every active query that includes the field automatically refreshes**. +* When you modify a cached field with [`writeQuery`](../caching/cache-interaction#writequery) or [`writeFragment`](../caching/cache-interaction#writefragment), **every active query that includes the field automatically refreshes**. #### Example @@ -242,7 +242,7 @@ const IS_LOGGED_IN = gql` `; ``` -The `isLoggedIn` field of this query is a local-only field. We can use [the `writeQuery` method](../caching/cache-interaction/#writequery-and-writefragment) to write a value for this field directly to the Apollo Client cache, like so: +The `isLoggedIn` field of this query is a local-only field. We can use [the `writeQuery` method](../caching/cache-interaction/#writequery) to write a value for this field directly to the Apollo Client cache, like so: ```js cache.writeQuery({