Skip to content

Commit

Permalink
Merge branch 'main' into release-3.5.
Browse files Browse the repository at this point in the history
  • Loading branch information
benjamn committed Aug 2, 2021
2 parents 1f29871 + 9329fd9 commit 962ec16
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 59 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## Apollo Client 3.4.2

### Bug Fixes

- Use more default type parameters for mutation-related types in `react/types/types.ts`, to provide smoother backwards compatibility for code using those types explicitly. <br/>
[@benjamn](https://github.com/benjamn) in [#8573](https://github.com/apollographql/apollo-client/pull/8573)

## Apollo Client 3.4.1

### Bug Fixes
Expand Down
6 changes: 3 additions & 3 deletions docs/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
},
"devDependencies": {
"typedoc": "0.15.8",
"typescript": "3.9.10"
"typescript": "4.3.5"
}
}
2 changes: 1 addition & 1 deletion docs/source/api/link/apollo-link-retry.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ These two features are combined to help alleviate [the thundering herd problem](

Instead of the options object, you may pass a function for `delay` and/or `attempts`, which implement custom strategies for each. In both cases the function is given the same arguments (`count`, `operation`, `error`).

The `attempts` function should return a boolean indicating whether the response should be retried. If yes, the `delay` function is then called, and should return the number of milliseconds to delay by.
The `attempts` function should return a `boolean` (or a `Promise` which resolves to a `boolean`) indicating whether the response should be retried. If yes, the `delay` function is then called, and should return the number of milliseconds to delay by.

```js
import { RetryLink } from "@apollo/client/link/retry";
Expand Down
2 changes: 1 addition & 1 deletion docs/source/api/react/hooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ function useLazyQuery<TData = any, TVariables = OperationVariables>(
options?: LazyQueryHookOptions<TData, TVariables>,
): [
(options?: QueryLazyOptions<TVariables>) => void,
QueryResult<TData, TVariables>
LazyQueryResult<TData, TVariables>
] {}
```

Expand Down
52 changes: 38 additions & 14 deletions docs/source/local-state/managing-state-with-field-policies.mdx
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
---
title: Local-only fields
title: Local-only fields in Apollo Client
sidebar_title: Local-only fields
description: Fetch both local and remote data with the same GraphQL query
description: Fetch local and remote data with one GraphQL query
---

import { ExpansionPanel } from 'gatsby-theme-apollo-docs/src/components/expansion-panel';

Your Apollo Client queries can include **local-only fields** that _aren't_ defined in your GraphQL server's schema. The values for these fields are calculated locally using any logic you want, such as reading data from `localStorage`.
Your Apollo Client queries can include **local-only fields** that _aren't_ defined in your GraphQL server's schema:

A single query can include both local-only fields _and_ fields that are fetched from your GraphQL server.
```graphql{5}
query ProductDetails($productId: ID!) {
product(id: $productId) {
name
price
isInCart @client # This is a local-only field
}
}
```

The values for these fields are calculated locally using any logic you want, such as reading data from `localStorage`.

As shown, a query can include both local-only fields _and_ fields that are fetched from your GraphQL server.

## Defining

Let's say we're building an e-commerce application. Most of a product's details are stored on our back-end server, but we want to define an `isInCart` boolean field that's local to the client. First, we create a **field policy** for `isInCart`.
Let's say we're building an e-commerce application. Most of a product's details are stored on our back-end server, but we want to define a `Product.isInCart` boolean field that's local to the client. First, we create a **field policy** for `isInCart`.

A field policy specifies custom logic for how a single GraphQL field is fetched from and written to your Apollo Client cache. You can define field policies for both local-only fields and remotely fetched fields.

Expand Down Expand Up @@ -97,14 +109,14 @@ You can use Apollo Client to query local state, regardless of how you _store_ th
* [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-state/local-resolvers/#local-resolvers). If you want to update local state, we recommend using [`writeQuery`](/caching/cache-interaction/#writequery), [`writeFragment`](/caching/cache-interaction/#writefragment), or [reactive variables](/local-state/reactive-variables/).
> **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-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](/local-state/reactive-variables/).
### Storing local state in reactive variables
Apollo Client [reactive variables](./reactive-variables) are great for representing local state:
* You can read and modify reactive variables from anywhere in your application, without needing to use a GraphQL operation to do so.
* Unlike the Apollo Client cache, reactive variables don't enforce data normalization, meaning you can store data in any format you want.
* Unlike the Apollo Client cache, reactive variables don't enforce [data normalization](../caching/overview/#data-normalization), which means you can store data in any format you want.
* If a field's value depends on the value of a reactive variable, and that variable's value _changes_, **every active query that includes the field automatically refreshes**.
#### Example
Expand All @@ -119,13 +131,15 @@ export const GET_CART_ITEMS = gql`
`;
```
Let's initialize a reactive variable to store our local list of cart items, like so:
Let's use the `makeVar` function to initialize a reactive variable that stores our local list of cart items:

```js{3}:title=cache.js
import { makeVar } from '@apollo/client';

```js:title=cache.js
export const cartItemsVar = makeVar([]);
```

This initializes a reactive variable that contains an empty array. We can get this variable's current value by calling `cartItemsVar()`, and we can set a _new_ value by calling `cartItemsVar(newValue)`.
This initializes a reactive variable with an empty array (you can pass any initial value to `makeVar`). Note that the return value of `makeVar` isn't the variable itself, but rather a _function_. We get the variable's current value by calling `cartItemsVar()`, and we set a _new_ value by calling `cartItemsVar(newValue)`.

Next, let's define the field policy for `cartItems`. As always, we pass this to the constructor of `InMemoryCache`:

Expand Down Expand Up @@ -198,9 +212,9 @@ export function Cart() {
}
```

Alternatively, you can read directly from a reactive variable using the `useReactiveVar` hook introduced in Apollo Client 3.2.0:
Instead of querying for `cartItems`, the `Cart` component can read and react to a reactive variable directly with the `useReactiveVar` hook:

```jsx:title=Cart.js
```jsx{1,4}:title=Cart.js
import { useReactiveVar } from '@apollo/client';

export function Cart() {
Expand All @@ -223,7 +237,9 @@ export function Cart() {
}
```

As in the earlier `useQuery` example, whenever the `cartItemsVar` variable is updated, any currently-mounted `Cart` components will rerender. Calling `cartItemsVar()` without `useReactiveVar` will not capture this dependency, so future variable updates will not rerender the component. Both of these approaches are useful in different situations.
As with the preceding `useQuery` example, whenever the `cartItemsVar` variable is updated, the `Cart` component rerenders.

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

Expand Down Expand Up @@ -321,11 +337,19 @@ ReactDOM.render(

Note that even if you _do_ store local data as fields in the Apollo Client cache, you can (and probably should!) still define `read` functions for those fields. A `read` function can execute helpful custom logic, such as returning a default value if a field isn't present in the cache.

### Persisting local state across sessions

By default, neither a reactive variable nor the `InMemoryCache` persists its state across sessions (for example, if a user refreshes their browser). To persist this state, you need to add logic to do so.

The `apollo-3-cache-persist` library helps you persist and rehydrate the Apollo Client cache between sessions. For details, see [Persisting the cache](../caching/advanced-topics/#persisting-the-cache).

There is currently no built-in API for persisting reactive variables, but you can write variable values to `localStorage` (or another store) whenever they're modified, and initialize those variables with their stored value (if any) on app load.

## Modifying

The way you modify the value of a local-only field depends on how you [store that field](#storing):

* **If you're using a [reactive variable](#storing-local-state-in-reactive-variables)**, all you need to do is set the reactive variable's new value. Apollo Client automatically detects this change and triggers a refresh of every active operation that includes an affected field.
* **If you're using a [reactive variable](#storing-local-state-in-reactive-variables)**, all you do is set the reactive variable's new value. Apollo Client automatically detects this change and triggers a refresh of every active operation that includes an affected field.

* **If you're [using the cache directly](#storing-local-state-in-the-cache)**, call one of `writeQuery`, `writeFragment`, or `cache.modify` ([all documented here](../caching/cache-interaction/)) to modify cached fields. Like reactive variables, all of these methods trigger a refresh of every affected active operation.

Expand Down
8 changes: 6 additions & 2 deletions docs/source/local-state/reactive-variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ title: Reactive variables
description: State containers integrated into Apollo Client's reactivity model
---

New in Apollo Client 3, **reactive variables** are a useful mechanism for storing local state outside of the Apollo Client cache. Because they're separate from the cache, reactive variables can store data of any type and structure, and you can interact with them anywhere in your application without using GraphQL syntax.
New in Apollo Client 3, **reactive variables** are a useful mechanism for representing local state outside of the Apollo Client cache. Because they're separate from the cache, reactive variables can store data of any type and structure, and you can interact with them anywhere in your application without using GraphQL syntax.

**Most importantly, modifying a reactive variable triggers an update of every active query that depends on that variable, as well an update of the react state associated with any variable values returned from the `useReactiveVar` React hook.** A query depends on a reactive variable if any of the query's requested fields defines a [`read` function](../caching/cache-field-behavior/#the-read-function) that reads the variable's value.
**Most importantly, modifying a reactive variable triggers an update of every active query that depends on that variable.** Additionally, this updates the React state for components that use the `useReactiveVar` React hook.

> A query "depends on" a reactive variable if any of the query's requested fields defines a [`read` function](../caching/cache-field-behavior/#the-read-function) that reads the variable's value.
## Creating

Expand Down Expand Up @@ -54,6 +56,8 @@ console.log(cartItemsVar());

As their name suggests, reactive variables can trigger reactive changes in your application. Whenever you modify the value of a reactive variable, queries that depend on that variable refresh, and your application's UI updates accordingly.

With the `useReactiveVar` hook, React components can also include reactive variable values in their state directly, _without_ wrapping them in a query.

For more information, see [Storing local state in reactive variables](./managing-state-with-field-policies/#storing-local-state-in-reactive-variables).

## Example application
Expand Down
45 changes: 24 additions & 21 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
"maxSize": "24.6 kB"
}
],
"engines": {
"npm": "^7.20.3"
},
"peerDependencies": {
"graphql": "^14.0.0 || ^15.0.0",
"react": "^16.8.0 || ^17.0.0",
Expand Down Expand Up @@ -86,7 +89,7 @@
"zen-observable-ts": "^1.1.0"
},
"devDependencies": {
"@babel/parser": "7.14.8",
"@babel/parser": "7.14.9",
"@graphql-tools/schema": "7.1.5",
"@rollup/plugin-node-resolve": "11.2.1",
"@testing-library/react": "9.5.0",
Expand All @@ -96,7 +99,7 @@
"@types/hoist-non-react-statics": "3.3.1",
"@types/jest": "26.0.24",
"@types/lodash": "4.14.171",
"@types/node": "16.4.3",
"@types/node": "16.4.10",
"@types/react": "17.0.15",
"@types/react-dom": "17.0.2",
"@types/recompose": "0.30.8",
Expand All @@ -123,7 +126,7 @@
"terser": "5.7.1",
"ts-jest": "26.5.6",
"ts-node": "10.1.0",
"typescript": "4.3.3",
"typescript": "4.3.5",
"wait-for-observables": "1.0.3"
},
"publishConfig": {
Expand Down
31 changes: 18 additions & 13 deletions src/react/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ export type RefetchQueriesFunction = (
) => InternalRefetchQueriesInclude;

export interface BaseMutationOptions<
TData,
TVariables extends OperationVariables,
TContext extends DefaultContext = DefaultContext,
TData = any,
TVariables = OperationVariables,
TContext = DefaultContext,
TCache extends ApolloCache<any> = ApolloCache<any>
> extends Omit<
MutationOptions<TData, TVariables, TContext, TCache>,
Expand All @@ -156,10 +156,10 @@ export interface BaseMutationOptions<
}

export interface MutationFunctionOptions<
TData,
TVariables,
TContext,
TCache extends ApolloCache<any>,
TData = any,
TVariables = OperationVariables,
TContext = DefaultContext,
TCache extends ApolloCache<any> = ApolloCache<any>,
> extends BaseMutationOptions<TData, TVariables, TContext, TCache> {
mutation?: DocumentNode | TypedDocumentNode<TData, TVariables>;
}
Expand Down Expand Up @@ -191,19 +191,24 @@ export interface MutationHookOptions<
}

export interface MutationDataOptions<
TData,
TVariables extends OperationVariables,
TContext extends DefaultContext,
TCache extends ApolloCache<any>,
TData = any,
TVariables = OperationVariables,
TContext = DefaultContext,
TCache extends ApolloCache<any> = ApolloCache<any>,
> extends BaseMutationOptions<TData, TVariables, TContext, TCache> {
mutation: DocumentNode | TypedDocumentNode<TData, TVariables>;
}

export type MutationTuple<TData, TVariables, TContext, TCache extends ApolloCache<any>> = [
export type MutationTuple<
TData,
TVariables,
TContext = DefaultContext,
TCache extends ApolloCache<any> = ApolloCache<any>,
> = [
(
options?: MutationFunctionOptions<TData, TVariables, TContext, TCache>
) => Promise<FetchResult<TData>>,
MutationResult<TData>
MutationResult<TData>,
];

/* Subscription types */
Expand Down

0 comments on commit 962ec16

Please sign in to comment.