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

Additional documentation for managing local state #10282

Merged
merged 5 commits into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

### Improvements

- Additional documentation for managing local state
[@bignimbus](https://github.com/bignimbus) in [#10282](https://github.com/apollographql/apollo-client/pull/10282)
- Only show dev tools suggestion in the console when `connectToDevTools` is `true`. <br/>
[@chris110408](https://github.com/chris110408) in [#10258](https://github.com/apollographql/apollo-client/pull/10258)
- docs: displays the error correctly<br/>
Expand Down
44 changes: 39 additions & 5 deletions docs/source/local-state/managing-state-with-field-policies.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,40 @@ You can use `read` functions to perform any sort of logic you want, including:

> 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.

### 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:

<ExpansionPanel title="See example">

```js
new InMemoryCache({
typePolicies: {
Person: {
fields: {
isInCart: {
read(_, { variables, storage }) {
Copy link
Member

Choose a reason for hiding this comment

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

I tend to think of an async read function as something that would return a promise and the wording in the sentence above would lead me to believe I'd see something like an async read(...) in here. Based on this example, it looks like this read function is still synchronous, just with the a side-effect that the storage variable is updated sometime after the first read function is kicked off.

Should this truly be an async function (e.g. async read()), or should we tweak the language above to better reflect the behavior in the example? I'm not sure of the intent on the comment about async reads.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing this out. It looks like an async function would be the ideal outcome of a future feature. This workaround, though, doesn't use a promise. I tried wordsmithing the above a bit. How's this?

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 ae6da0682..91131ac08 100644
--- a/docs/source/local-state/managing-state-with-field-policies.mdx
+++ b/docs/source/local-state/managing-state-with-field-policies.mdx
@@ -62,7 +62,7 @@ You can use `read` functions to perform any sort of logic you want, including:
 
 ### Reads are synchronous by design
 
-Many UI frameworks like React (without `Suspense`) have synchronous rendering pipelines so it's important for UI components to have immediate access to any existing data.  So 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 roll your own asynchronous `read` function:
+Many UI frameworks like React 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:
 
 <ExpansionPanel title="See example">
 

Copy link
Member

Choose a reason for hiding this comment

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

I like that much better!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated - thanks!

if (!storage.var) {
storage.var = makeVar(false);
setTimeout(() => {
storage.var(
localStorage.getItem('CART').includes(
variables.productId
)
);
}, 100);
}
return storage.var();
}
}
}
}
}
})
```

</ExpansionPanel>

## Querying

Now that we've defined a field policy for `isInCart`, we can include the field in a query that _also_ queries our back-end server, like so:
Expand Down Expand Up @@ -99,16 +133,16 @@ That's it! The `@client` directive tells Apollo Client that `isInCart` is a loca
>
> </ExpansionPanel>

## Storing
## Storing and mutations

You can use Apollo Client to query local state, regardless of how you _store_ that state. Apollo Client provides a couple of optional but helpful mechanisms for representing local state:
You can use Apollo Client to query and modify local state, regardless of how you _store_ that state. Apollo Client provides a couple of optional but helpful mechanisms for representing local state:

* [Reactive variables](#storing-local-state-in-reactive-variables)
* [The Apollo Client cache itself](#storing-local-state-in-the-cache)

> **Note:** Apollo Client >= 3 no longer recommends the [local resolver](./local-resolvers) approach of using `client.mutate` / `useMutation` combined with an `@client` mutation operation to [update local state](./local-resolvers/#local-resolvers). To update local state, we recommend using [`writeQuery`](../caching/cache-interaction/#writequery), [`writeFragment`](../caching/cache-interaction/#writefragment), or [reactive variables](./reactive-variables/).
It's tempting to think of local-only mutations as being expressed similarly to other local-only fields. When using previous versions of Apollo Client, developers would define local-only mutations using `@client`. They would then use [local resolvers](./local-resolvers) to handle `client.mutate` / `useMutation` calls. This is no longer the case, however. For Apollo Client version >= 3 users we recommend using [`writeQuery`](../caching/cache-interaction/#writequery), [`writeFragment`](../caching/cache-interaction/#writefragment), or [reactive variables](./reactive-variables/) to manage local state.

### Storing local state in reactive variables
### Storing and updating local state with reactive variables

Apollo Client [reactive variables](./reactive-variables) are great for representing local state:

Expand Down Expand Up @@ -238,7 +272,7 @@ As with the preceding `useQuery` example, whenever the `cartItemsVar` variable i

> **Important:** If you call `cartItemsVar()` instead of `useReactiveVar(cartItemsVar)` in the example above, future variable updates _do not_ cause the `Cart` component to rerender.

### Storing local state in the cache
### Storing and modifying local state in the cache

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):

Expand Down