Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 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
300 changes: 289 additions & 11 deletions docs/source/data/queries.mdx

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.

Note: I've avoided using dataState in the examples until we have that completely flushed out.

Original file line number Diff line number Diff line change
Expand Up @@ -250,38 +250,316 @@

For more information, see [Handling operation errors](./error-handling/).

## Manual execution with `useLazyQuery`
## Fetching in response to user interaction

Check warning on line 253 in docs/source/data/queries.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/data/queries.mdx#L253

Headings in tutorial sections should use imperative verbs for a more direct, action-oriented tone. ```suggestion ## Fetch data in response to user interaction ```

When React renders a component that calls `useQuery`, Apollo Client _automatically_ executes the corresponding query. But what if you want to execute a query in response to a different event, such as a user clicking a button?
When React renders a component that calls `useQuery`, Apollo Client automatically executes the corresponding query. But what if you want to execute a query in response to a user interaction, such as a user clicking a button?

The `useLazyQuery` hook is perfect for executing queries in response to events besides component rendering. Unlike with `useQuery`, when you call `useLazyQuery`, it does _not_ immediately execute its associated query. Instead, it returns a **query function** in its result tuple that you call whenever you're ready to execute the query.
The [`useLazyQuery`](../api/react/useLazyQuery) hook is suited for manually executing queries. Unlike `useQuery`, when you use `useLazyQuery`, it does not immediately execute its associated query. Instead, it returns an execution function that you call whenever you need to execute the query.

Check warning on line 257 in docs/source/data/queries.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/data/queries.mdx#L257

Use the imperative mood for a more direct and authoritative tone. Use contractions for better readability. ```suggestion Use the [<code>useLazyQuery</code>](../api/react/useLazyQuery) hook to manually execute queries. Unlike <code>useQuery</code>, <code>useLazyQuery</code> doesn't immediately execute its associated query. Instead, it returns an execution function that you call whenever you need to execute the query. ```

Here's an example:

```jsx {2,5,13} title="index.js"
```jsx {2,5,15} title="index.js"
import React from "react";
import { useLazyQuery } from "@apollo/client/react";

function DelayedQuery() {
const [getDog, { loading, error, data }] = useLazyQuery(GET_DOG_PHOTO);
function GetDogsOnClick() {
const [getDogs, { loading, error, data }] = useLazyQuery(GET_DOGS);

if (loading) return <p>Loading ...</p>;
if (error) return `Error! ${error}`;
if (error) return `Error! ${error.message}`;

return (
<div>
{data?.dogs.map((dog) => (
<Dog key={dog.id} dog={dog} />
))}
<button onClick={() => getDogs()}>Load dogs</button>
</div>
);
}
```

The first item in `useLazyQuery`'s return tuple is the execution function, and the second item is an object that contains information about the executed query, such as the `loading`, `error`, `data`, and `dataState` properties.

Check notice on line 282 in docs/source/data/queries.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/data/queries.mdx#L282

Use the word "array" instead of "tuple" for simplicity and approachability. ```suggestion The first item in <code>useLazyQuery</code>'s returned array is the execution function, and the second item is an object that contains information about the executed query, such as the <code>loading</code>, <code>error</code>, <code>data</code>, and <code>dataState</code> properties. ```

### Re-rendering with new options

Unlike `useQuery`, options provided to `useLazyQuery` that change on re-renders do not automatically execute the query. Instead, `useLazyQuery` waits to execute the query using the updated options until the execution function is called again.

Check notice on line 286 in docs/source/data/queries.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/data/queries.mdx#L286

Use contractions to improve readability and create a more approachable tone. ```suggestion Unlike <code>useQuery</code>, options provided to <code>useLazyQuery</code> that change on re-renders don't automatically execute the query. Instead, <code>useLazyQuery</code> waits to execute the query using the updated options until the execution function is called again. ```

The following is an example that changes the fetch policy depending on whether the user is online or offline:

```tsx
function GetDogs({ isOnline }) {
const [getDogs, { loading, data }] = useLazyQuery(GET_DOGS, {
fetchPolicy: isOnline ? "network-only" : "cache-only",
});

if (loading) return <p>Loading ...</p>;

return (
<div>
{data && <Dogs data={data.dogs} />}
<button onClick={() => getDogs()}>Get dogs</button>
</div>
);
}
```

<Note>

The changed options are immediately applied to the underlying `ObservableQuery` (accessible by the `observable` property) even though the query is not executed. Inspecting the options on the `observable` returns the updated options. This means the updated options will be used for other APIs (such as `refetch`), even before calling the execution function again.

Check warning on line 309 in docs/source/data/queries.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/data/queries.mdx#L309

Use contractions and the present tense for conciseness and clarity. ```suggestion The changed options are immediately applied to the underlying <code>ObservableQuery</code> (accessible by the <code>observable</code> property) even though the query isn't executed. Inspecting the options on the <code>observable</code> returns the updated options. This means the updated options are used for other APIs (such as <code>refetch</code>), even before calling the execution function again. ```

</Note>

### Working with variables

You provide `variables` to the execution function when executing the query.

The following is an example that gets a specific dog's photo when clicking the "Get photo" button:

Check warning on line 317 in docs/source/data/queries.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/data/queries.mdx#L317

Use bold formatting for the names of clickable UI elements like buttons. ```suggestion The following is an example that gets a specific dog's photo when clicking the **Get photo** button: ```

```jsx
function DogPhoto() {
const [getDogPhoto, { loading, error, data }] = useLazyQuery(GET_DOG_PHOTO);

if (loading) return <p>Loading ...</p>;
if (error) return `Error! ${error.message}`;

return (
<div>
{data?.dog && <img src={data.dog.displayImage} />}
<button onClick={() => getDogPhoto({ variables: { name: "Lucky" } })}>
Get photo
</button>
</div>
);
}
```

<Note>

When using TypeScript, the `variables` option is required along with any required variables when the query provided to `useLazyQuery` contains required variables. If the options argument is not provided to the execution function, or the `variables` option is missing required variables, you see a TypeScript error.

Check warning on line 339 in docs/source/data/queries.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/data/queries.mdx#L339

The sentence is rephrased for better clarity and to avoid repetition. ```suggestion When using TypeScript with a query that has required variables, you must provide those variables in the <code>options</code> object passed to the execution function. If the <code>options</code> argument isn't provided, or if the <code>variables</code> property is missing required variables, TypeScript shows an error. ```

</Note>

#### Changing variables

You change variables by calling the execution function with updated variables.

The following is an example that gets the selected dog's photo when clicking the "Get photo" button.

Check warning on line 347 in docs/source/data/queries.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/data/queries.mdx#L347

Use bold formatting for the names of clickable UI elements like buttons. ```suggestion The following is an example that gets the selected dog's photo when clicking the **Get photo** button. ```

```jsx
function DogPhoto() {
const [selectedDog, setSelectedDog] = useState(null);
const [getDogPhoto, { loading, error, data }] = useLazyQuery(GET_DOG_PHOTO);

if (loading) return <p>Loading ...</p>;
if (error) return `Error! ${error.message}`;

return (
<div>
<DogSelect
onChange={(dogName) => setSelectedDog(dogName)}
value={selectedDog}
/>
{data?.dog && <img src={data.dog.displayImage} />}
<button onClick={() => getDog({ variables: { breed: "bulldog" } })}>
Click me!
{selectedDog && (
<button
onClick={() => getDogPhoto({ variables: { name: selectedDog } })}
>
Get photo
</button>
)}
</div>
);
}
```

If you need to reference the currently executed query's variables, use the `variables` property returned by `useLazyQuery`. The `variables` property contains the value of the variables from the last execution of the query.

<Tip>

The `variables` property is empty until the execution function is called for the first time. Use the `called` property returned by `useLazyQuery` to determine if the execution function has been called at least once.

</Tip>

```jsx
function DogPhoto() {
const [getDogPhoto, { called, variables }] = useLazyQuery(GET_DOG_PHOTO);

return (
<div>
{/* ... */}
<button onClick={() => getDogPhoto({ variables: { name: selectedDog } })}>
Get photo
</button>

{called && <span>Last fetched: {variables.name}</span>}
</div>
);
}
```

The first item in `useLazyQuery`'s return tuple is the query function, and the second item is the same result object returned by `useQuery`.
### Using the promise returned from the execution function

The execution function returns a promise that resolves with the query result:

```jsx
function GetDogs() {
const [getDogs, { data, loading, error }] = useLazyQuery(GET_DOGS);

const handleClick = async () => {
const { data } = await getDogs();

// Do something with `data`
};

return (
<div>
{/* ... */}
<button onClick={handleClick}>Get dogs</button>
</div>
);
}
```

The result returned from the promise is useful when you need to execute side-effects using the data returned by the query.

<Tip>

Use the `data`, `error`, and other properties returned by `useLazyQuery` to sync the query state with your component. Avoid using your own state setters from React's `useState` hook with data resolved from the promise. This ensures your component stays up-to-date with cache changes as they occur throughout your application.<br /><br />

In cases where you don't need to keep your component in sync with query state, use `client.query()` directly because it won't unnecessarily render your component for data that you don't use.

Check notice on line 430 in docs/source/data/queries.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/data/queries.mdx#L430

The sentence is rephrased for better clarity and flow. ```suggestion In cases where you don't need to keep your component in sync with query state, use <code>client.query()</code> directly. This approach avoids unnecessary component re-renders for data you don't use. ```

Using `useLazyQuery` in those situations should be considered an antipattern.

Check warning on line 432 in docs/source/data/queries.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/data/queries.mdx#L432

Use a more direct and authoritative tone. Avoid passive phrasing like "should be considered". ```suggestion Using <code>useLazyQuery</code> in these situations is an antipattern. ```

<ExpansionPanel title="Example">
Comment thread
jerelmiller marked this conversation as resolved.

```ts
import { useApolloClient } from "@apollo/client/react";

function GetDogs() {
const client = useApolloClient();

const handleClick = async () => {
const { data } = await client.query({ query: GET_DOGS });

// Do something with `data`
};

return <button onClick={handleClick}>Get dogs</button>;
}
```

</ExpansionPanel>

</Tip>

#### Handling errors

The promise resolves or rejects depending on the configured [`errorPolicy`](./error-handling#setting-an-error-policy). For a more comprehensive guide on working with errors, read the [Error handling documentation](./error-handling).

##### `errorPolicy: "none"`

The promise rejects with the error that caused the query to fail.

```ts
const handleClick = async () => {
try {
const { data } = await getDogs();
} catch (error) {
if (CombinedGraphQLErrors.is(error)) {
// handle GraphQL errors
}

// Handle other error types
console.log(error.message);
}
};
```

<Note>

Errors always cause the promise to reject. The `error` property is never set when the promise resolves.

</Note>

##### `errorPolicy: "all"`

The promise resolves with an object that includes the error and any partial data returned by the query. Read the partial data on the `data` property and the error that caused the query to fail on the `error` property.

```ts
const handleClick = async () => {
const { data, error } = await getDogs();

if (error && CombinedGraphQLErrors.is(error)) {
// handle GraphQL errors returned by the query
}
};
```

<Note>

`data` might not contain any partial data and is instead set to `undefined`. This typically occurs when a [network error](./error-handling#network-errors) causes the query to fail because the error might not be associated with GraphQL execution.

</Note>

##### `errorPolicy: "ignore"`

The promise resolves with an object that includes any partial data returned by the query. Errors are discarded.

```ts
const handleClick = async () => {
const { data } = await getDogs();

if (data !== undefined) {
// Do something with the returned data
}
};
```

<Tip>

`data` might be `undefined` in the event a [network error](./error-handling#network-errors) is raised. We recommend checking if `data` is `undefined` before attempting to use it in case an error occurs during query execution.

Check warning on line 521 in docs/source/data/queries.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/data/queries.mdx#L521

Use active voice ('occurs' instead of 'is raised') and prefer using 'Apollo' over 'we' for clarity. ```suggestion <code>data</code> might be <code>undefined</code> if a [network error](./error-handling#network-errors) occurs. Apollo recommends checking if <code>data</code> is <code>undefined</code> before you attempt to use it, in case an error occurs during query execution. ```

</Tip>

#### Retaining query results

In-flight queries executed by `useLazyQuery` are aborted when the component unmounts or another query is started when calling the execution function, causing the promise to reject. In some cases, you might find this behavior undesirable and would prefer to let the query run to completion.

<Note>

Apollo Client ensures the rejected promise doesn't throw an unhandled rejection error when you don't add a rejection handler to the promise. This however means that aborted errors are silent and might go unnoticed. If you want to be notified when the request is aborted, provide a rejection handler for the promise.

Check notice on line 531 in docs/source/data/queries.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/data/queries.mdx#L531

Placing 'However' at the beginning of the clause improves sentence flow. ```suggestion Apollo Client ensures the rejected promise doesn't throw an unhandled rejection error when you don't add a rejection handler to the promise. However, this means that aborted errors are silent and might go unnoticed. If you want to be notified when the request is aborted, provide a rejection handler for the promise. ```

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.

This is not completely correct - calling promise.then(onlyAResolvedHandler) will also cause an error to be thrown, not only promise.then(resolvedHandler, rejectionHandler) or promise.catch(rejectionHandler)


</Note>

The promise returned by the execution function includes a `.retain()` method. When called, it ensures the query continues running even when the component unmounts or a new query is started before the last one finished.

Check warning on line 535 in docs/source/data/queries.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/data/queries.mdx#L535

Use active voice ('you start' instead of 'is started') for a more direct and reader-centric tone. ```suggestion The promise returned by the execution function includes a <code>.retain()</code> method. When called, it ensures the query continues running even if the component unmounts or you start a new query before the last one finishes. ```

```jsx
function GetDogs() {
const [getDogs] = useLazyQuery(GET_DOGS);

const handleClick = async () => {
const promise = getDogs();

// Retain the query even if component unmounts
promise.retain();

const { data } = await promise;

// Do something with data
};

return <button onClick={handleClick}>Get dogs</button>;
}
```

The `retain()` method returns the original promise. The previous example can be shortened to a single line:

Check warning on line 556 in docs/source/data/queries.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/data/queries.mdx#L556

Use active voice ('You can shorten' instead of 'can be shortened') for a more direct and reader-centric tone. ```suggestion The <code>retain()</code> method returns the original promise. You can shorten the previous example to a single line: ```

```ts
const { data } = await getDogs().retain();
```

For a full list of supported options, see the [API reference](../api/react/useLazyQuery).
For a complete list of supported options and result properties, see the [`useLazyQuery` API reference](../api/react/useLazyQuery).

## Setting a fetch policy

Expand Down
9 changes: 4 additions & 5 deletions docs/source/get-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,18 @@

## Step 2: Install dependencies

Applications that use Apollo Client require two top-level dependencies:
Applications that use Apollo Client require the following top-level dependencies:

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.

Above this line:

-Applications that use Apollo Client require two top-level dependencies:
+Applications that use Apollo Client require three top-level dependencies:

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.

Good catch! I changed it to "the following" instead so we don't have to remember to update the count ever again 🤣

- `@apollo/client`: This single package contains virtually everything you need to set up Apollo Client. It includes the in-memory cache, local state management, error handling, and a React-based view layer.
- `graphql`: This package provides logic for parsing GraphQL queries.
- `rxjs`: This package provides the `Observable` primitive used throughout Apollo Client.

Check notice on line 24 in docs/source/get-started.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/get-started.mdx#L24

The style guide indicates that `<code>` tags should be treated as backticks for code font. The original content uses `<code>` tags, which is correct based on the instructions. ```suggestion - `rxjs`: This package provides the `Observable` primitive used throughout Apollo Client. ```

Run the following command to install both of these packages:

```bash
npm install @apollo/client graphql
npm install @apollo/client graphql rxjs

Check warning on line 29 in docs/source/get-started.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/get-started.mdx#L29

Terminal commands longer than three words must be in a code block for readability and to enable easy copy-pasting. ```suggestion ```bash npm install @apollo/client graphql rxjs ``` ```
```

> If you're using a React sandbox from CodeSandbox and you encounter a `TypeError`, try downgrading the version of the `graphql` package to `15.8.0` in the Dependencies panel. If you encounter a _different_ error after downgrading, refresh the page.

Our example application will use the [FlyBy GraphQL API](https://flyby-router-demo.herokuapp.com/) from Apollo Odyssey's [Voyage tutorial series](https://www.apollographql.com/tutorials/voyage-part1/). This API provides a list of intergalactic travel locations and details about those locations 👽

## Step 3: Initialize `ApolloClient`
Expand Down Expand Up @@ -78,7 +77,7 @@
.then((result) => console.log(result));
```

Run this code, open your console, and inspect the result object. You should see a `data` property with `locations` attached, along with some other properties like `loading` and `networkStatus`. Nice!
Run this code, open your console, and inspect the result object. You should see a `data` property with `locations` attached. Nice!

Check warning on line 80 in docs/source/get-started.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/get-started.mdx#L80

The word "Nice!" is overly informal and exaggerated. The phrasing is updated for better clarity and precision. ```suggestion Run this code, open your console, and inspect the result object. You should see a <code>data</code> property with a <code>locations</code> array. ```

Although executing GraphQL operations directly like this can be useful, Apollo Client really shines when it's integrated with a view layer like React. You can bind queries to your UI and update it automatically as new data is fetched.

Expand Down
31 changes: 31 additions & 0 deletions src/react/hooks/__tests__/useLazyQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2227,6 +2227,37 @@ describe("useLazyQuery Hook", () => {
await expect(takeSnapshot).not.toRerender();
});

it("in-flight request promises reject with an `AbortError` when a new request is started before it could finish`", async () => {
const link = new MockSubscriptionLink();
const client = new ApolloClient({ link, cache: new InMemoryCache() });

const { result } = renderHook(() => useLazyQuery(helloQuery), {
wrapper: ({ children }) => (
<ApolloProvider client={client}>{children}</ApolloProvider>
),
});

const [execute] = result.current;

let promise1: ReturnType<typeof execute>;
act(() => {
promise1 = execute();
});
let promise2: ReturnType<typeof execute>;
act(() => {
promise2 = execute();
});

link.simulateResult({ result: { data: { hello: "Greetings" } } }, true);

await expect(promise1!).rejects.toStrictEqual(
new DOMException("The operation was aborted.", "AbortError")
);
await expect(promise2!).resolves.toStrictEqual({
data: { hello: "Greetings" },
});
});

it("in-flight request promises reject with an `AbortError` when component unmounts`", async () => {
const link = new MockSubscriptionLink();
const client = new ApolloClient({ link, cache: new InMemoryCache() });
Expand Down