mirror of
https://github.com/zhigang1992/apollo.git
synced 2026-01-12 22:45:53 +08:00
Merge pull request #540 from apollographql/hooks-conversion
Hooks conversion
This commit is contained in:
@@ -84,19 +84,17 @@ const GET_DOGS = gql`
|
||||
|
||||
Here, we're describing the shape of the object we want to receive from the server. GraphQL takes care of combining and filtering the data while returning exactly what we ask for.
|
||||
|
||||
How do we use this query in our app? Apollo Client builds off of GraphQL's declarative approach to data fetching. In a React app, all of the logic for retrieving your data, tracking loading and error states, and updating your UI is encapsulated in a single `Query` component. This encapsulation makes composing your data fetching components with your presentational components a breeze! Let’s see how to fetch GraphQL data with Apollo Client in a React app:
|
||||
How do we use this query in our app? Apollo Client builds off of GraphQL's declarative approach to data fetching. In a React app, all of the logic for retrieving your data, tracking loading and error states, and updating your UI is encapsulated in a single `useQuery` hook. This encapsulation makes composing your data fetching components with your presentational components a breeze! Let’s see how to fetch GraphQL data with Apollo Client in a React app:
|
||||
|
||||
```jsx
|
||||
const Feed = () => (
|
||||
<Query query={GET_DOGS}>
|
||||
{({ loading, error, data }) => {
|
||||
if (error) return <Error />;
|
||||
if (loading || !data) return <Fetching />;
|
||||
function Feed() {
|
||||
const { loading, error, data } = useQuery(GET_DOGS);
|
||||
|
||||
return <DogList dogs={data.dogs} />;
|
||||
}}
|
||||
</Query>
|
||||
);
|
||||
if (error) return <Error />;
|
||||
if (loading || !data) return <Fetching />;
|
||||
|
||||
return <DogList dogs={data.dogs} />;
|
||||
}
|
||||
```
|
||||
|
||||
Apollo Client takes care of the request cycle from start to finish, including tracking loading and error states for you. There’s no middleware to set up or boilerplate to write before making your first request, nor do you need to worry about transforming and caching the response. All you have to do is describe the data your component needs and let Apollo Client do the heavy lifting. 💪
|
||||
|
||||
@@ -124,16 +124,16 @@ Go ahead and delete the `client.query()` call you just made and the `gql` import
|
||||
|
||||
## Connect your client to React
|
||||
|
||||
Connecting Apollo Client to our React app with `react-apollo` allows us to easily bind GraphQL operations to our UI.
|
||||
Connecting Apollo Client to our React app with Apollo's hooks allows us to easily bind GraphQL operations to our UI.
|
||||
|
||||
To connect Apollo Client to React, we will wrap our app in the `ApolloProvider` component exported from the `react-apollo` package and pass our client to the `client` prop. The `ApolloProvider` component is similar to React’s context provider. It wraps your React app and places the client on the context, which allows you to access it from anywhere in your component tree.
|
||||
To connect Apollo Client to React, we will wrap our app in the `ApolloProvider` component exported from the `@apollo/react-hooks` package and pass our client to the `client` prop. The `ApolloProvider` component is similar to React’s context provider. It wraps your React app and places the client on the context, which allows you to access it from anywhere in your component tree.
|
||||
|
||||
Open `src/index.js` and add the following lines of code:
|
||||
|
||||
_src/index.js_
|
||||
|
||||
```jsx{1,4,6}
|
||||
import { ApolloProvider } from 'react-apollo';
|
||||
```jsx
|
||||
import { ApolloProvider } from '@apollo/react-hooks';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Pages from './pages';
|
||||
@@ -147,4 +147,4 @@ ReactDOM.render(
|
||||
);
|
||||
```
|
||||
|
||||
Now, we're ready to start building our first `Query` components in the next section.
|
||||
Now, we're ready to start building our first component with the `useQuery` hook in the next section.
|
||||
|
||||
@@ -85,8 +85,8 @@ Let's look at an example where we query the `isLoggedIn` field we wrote to the c
|
||||
|
||||
_src/index.js_
|
||||
|
||||
```jsx{8,17-19}
|
||||
import { Query, ApolloProvider } from 'react-apollo';
|
||||
```jsx{8-12,15}
|
||||
import { ApolloProvider, Query } from '@apollo/react-hooks';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import Pages from './pages';
|
||||
@@ -99,18 +99,21 @@ const IS_LOGGED_IN = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
function IsLoggedIn() {
|
||||
const { data } = useQuery(IS_LOGGED_IN);
|
||||
return data.isLoggedIn ? <Pages /> : <Login />;
|
||||
}
|
||||
|
||||
injectStyles();
|
||||
ReactDOM.render(
|
||||
<ApolloProvider client={client}>
|
||||
<Query query={IS_LOGGED_IN}>
|
||||
{({ data }) => (data.isLoggedIn ? <Pages /> : <Login />)}
|
||||
</Query>
|
||||
<IsLoggedIn />
|
||||
</ApolloProvider>,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
```
|
||||
|
||||
First, we create our `IsUserLoggedIn` local query by adding the `@client` directive to the `isLoggedIn` field. Then, we render a `Query` component, pass our local query in, and specify a render prop function that renders either a login screen or the homepage depending if the user is logged in. Since cache reads are synchronous, we don't have to account for any loading state.
|
||||
First, we create our `IsUserLoggedIn` local query by adding the `@client` directive to the `isLoggedIn` field. Then, we render a component with `useQuery`, pass our local query in, and based on the response render either a login screen or the homepage depending if the user is logged in. Since cache reads are synchronous, we don't have to account for any loading state.
|
||||
|
||||
Let's look at another example of a component that queries local state in `src/pages/cart.js`. Just like before, we create our query:
|
||||
|
||||
@@ -118,7 +121,7 @@ _src/pages/cart.js_
|
||||
|
||||
```js
|
||||
import React, { Fragment } from 'react';
|
||||
import { Query } from 'react-apollo';
|
||||
import { useQuery } from '@apollo/react-hooks';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { Header, Loading } from '../components';
|
||||
@@ -131,34 +134,29 @@ export const GET_CART_ITEMS = gql`
|
||||
`;
|
||||
```
|
||||
|
||||
Next, we render our `Query` component and bind it to our `GetCartItems` query:
|
||||
Next, we call `useQuery` and bind it to our `GetCartItems` query:
|
||||
|
||||
_src/pages/cart.js_
|
||||
|
||||
```jsx
|
||||
export default function Cart() {
|
||||
const { data, loading, error } = useQuery(GET_CART_ITEMS);
|
||||
if (loading) return <Loading />;
|
||||
if (error) return <p>ERROR: {error.message}</p>;
|
||||
return (
|
||||
<Query query={GET_CART_ITEMS}>
|
||||
{({ data, loading, error }) => {
|
||||
if (loading) return <Loading />;
|
||||
if (error) return <p>ERROR: {error.message}</p>;
|
||||
return (
|
||||
<Fragment>
|
||||
<Header>My Cart</Header>
|
||||
{!data.cartItems || !data.cartItems.length ? (
|
||||
<p data-testid="empty-message">No items in your cart</p>
|
||||
) : (
|
||||
<Fragment>
|
||||
{data.cartItems.map(launchId => (
|
||||
<CartItem key={launchId} launchId={launchId} />
|
||||
))}
|
||||
<BookTrips cartItems={data.cartItems} />
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}}
|
||||
</Query>
|
||||
<Fragment>
|
||||
<Header>My Cart</Header>
|
||||
{!data.cartItems || !data.cartItems.length ? (
|
||||
<p data-testid="empty-message">No items in your cart</p>
|
||||
) : (
|
||||
<Fragment>
|
||||
{data.cartItems.map(launchId => (
|
||||
<CartItem key={launchId} launchId={launchId} />
|
||||
))}
|
||||
<BookTrips cartItems={data.cartItems} />
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
```
|
||||
@@ -226,33 +224,30 @@ Up until now, we've focused on querying local data from the Apollo cache. Apollo
|
||||
|
||||
### Direct cache writes
|
||||
|
||||
Direct cache writes are convenient when you want to write a simple field, like a boolean or a string, to the Apollo cache. We perform a direct write by calling `client.writeData()` and passing in an object with a data property that corresponds to the data we want to write to the cache. We've already seen an example of a direct write, when we called `client.writeData` in the `onCompleted` handler for the login `Mutation` component. Let's look at a similar example, where we copy the code below to create a logout button:
|
||||
Direct cache writes are convenient when you want to write a simple field, like a boolean or a string, to the Apollo cache. We perform a direct write by calling `client.writeData()` and passing in an object with a data property that corresponds to the data we want to write to the cache. We've already seen an example of a direct write, when we called `client.writeData` in the `onCompleted` handler for the login `useMutation` based component. Let's look at a similar example, where we copy the code below to create a logout button:
|
||||
|
||||
_src/containers/logout-button.js_
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import styled from 'react-emotion';
|
||||
import { ApolloConsumer } from 'react-apollo';
|
||||
import { useApolloClient } from '@apollo/react-hooks';
|
||||
|
||||
import { menuItemClassName } from '../components/menu-item';
|
||||
import { ReactComponent as ExitIcon } from '../assets/icons/exit.svg';
|
||||
|
||||
export default function LogoutButton() {
|
||||
const client = useApolloClient();
|
||||
return (
|
||||
<ApolloConsumer>
|
||||
{client => (
|
||||
<StyledButton
|
||||
onClick={() => {
|
||||
client.writeData({ data: { isLoggedIn: false } }); // highlight-line
|
||||
localStorage.clear();
|
||||
}}
|
||||
>
|
||||
<ExitIcon />
|
||||
Logout
|
||||
</StyledButton>
|
||||
)}
|
||||
</ApolloConsumer>
|
||||
<StyledButton
|
||||
onClick={() => {
|
||||
client.writeData({ data: { isLoggedIn: false } }); // highlight-line
|
||||
localStorage.clear();
|
||||
}}
|
||||
>
|
||||
<ExitIcon />
|
||||
Logout
|
||||
</StyledButton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -265,13 +260,13 @@ const StyledButton = styled('button')(menuItemClassName, {
|
||||
|
||||
When we click the button, we perform a direct cache write by calling `client.writeData` and passing in a data object that sets the `isLoggedIn` boolean to false.
|
||||
|
||||
We can also perform direct writes within the `update` function of a `Mutation` component. The `update` function allows us to manually update the cache after a mutation occurs without refetching data. Let's look at an example in `src/containers/book-trips.js`:
|
||||
We can also perform direct writes within the `update` function of the `useMutation` hook. The `update` function allows us to manually update the cache after a mutation occurs without refetching data. Let's look at an example in `src/containers/book-trips.js`:
|
||||
|
||||
_src/containers/book-trips.js_
|
||||
|
||||
```jsx{30-32}
|
||||
```jsx{29-31}
|
||||
import React from 'react';
|
||||
import { Mutation } from 'react-apollo';
|
||||
import { useMutation } from '@apollo/react-hooks';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import Button from '../components/button';
|
||||
@@ -291,29 +286,25 @@ const BOOK_TRIPS = gql`
|
||||
`;
|
||||
|
||||
export default function BookTrips({ cartItems }) {
|
||||
return (
|
||||
<Mutation
|
||||
mutation={BOOK_TRIPS}
|
||||
variables={{ launchIds: cartItems }}
|
||||
refetchQueries={cartItems.map(launchId => ({
|
||||
const [bookTrips, { data, loading, error }] = useMutation(
|
||||
BOOK_TRIPS,
|
||||
{
|
||||
refetchQueries: cartItems.map(launchId => ({
|
||||
query: GET_LAUNCH,
|
||||
variables: { launchId },
|
||||
}))}
|
||||
update={cache => {
|
||||
})),
|
||||
update(cache) {
|
||||
cache.writeData({ data: { cartItems: [] } });
|
||||
}}
|
||||
>
|
||||
{(bookTrips, { data, loading, error }) =>
|
||||
data && data.bookTrips && !data.bookTrips.success ? (
|
||||
<p data-testid="message">{data.bookTrips.message}</p>
|
||||
) : (
|
||||
<Button onClick={bookTrips} data-testid="book-button">
|
||||
Book All
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
</Mutation>
|
||||
);
|
||||
}
|
||||
)
|
||||
return data && data.bookTrips && !data.bookTrips.success
|
||||
? <p data-testid="message">{data.bookTrips.message}</p>
|
||||
: (
|
||||
<Button onClick={bookTrips} data-testid="book-button">
|
||||
Book All
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -368,7 +359,7 @@ _src/containers/action-button.js_
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import { Mutation } from 'react-apollo';
|
||||
import { useMutation } from '@apollo/react-hooks';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { GET_LAUNCH_DETAILS } from '../pages/launch';
|
||||
@@ -388,43 +379,41 @@ const CANCEL_TRIP = gql`
|
||||
`;
|
||||
|
||||
export default function ActionButton({ isBooked, id, isInCart }) {
|
||||
return (
|
||||
<Mutation
|
||||
mutation={isBooked ? CANCEL_TRIP : TOGGLE_CART}
|
||||
variables={{ launchId: id }}
|
||||
refetchQueries={[
|
||||
const [mutate, { loading, error }] = useMutation(
|
||||
isBooked ? CANCEL_TRIP : TOGGLE_CART,
|
||||
{
|
||||
variables: { launchId: id },
|
||||
refetchQueries: [
|
||||
{
|
||||
query: GET_LAUNCH_DETAILS,
|
||||
variables: { launchId: id },
|
||||
},
|
||||
]}
|
||||
>
|
||||
{(mutate, { loading, error }) => {
|
||||
if (loading) return <p>Loading...</p>;
|
||||
if (error) return <p>An error occurred</p>;
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
onClick={mutate}
|
||||
isBooked={isBooked}
|
||||
data-testid={'action-button'}
|
||||
>
|
||||
{isBooked
|
||||
? 'Cancel This Trip'
|
||||
: isInCart
|
||||
? 'Remove from Cart'
|
||||
: 'Add to Cart'}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Mutation>
|
||||
if (loading) return <p>Loading...</p>;
|
||||
if (error) return <p>An error occurred</p>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
onClick={mutate}
|
||||
isBooked={isBooked}
|
||||
data-testid={'action-button'}
|
||||
>
|
||||
{isBooked
|
||||
? 'Cancel This Trip'
|
||||
: isInCart
|
||||
? 'Remove from Cart'
|
||||
: 'Add to Cart'}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
In this example, we're using the `isBooked` prop passed into the component to determine which mutation we should fire. Just like remote mutations, we can pass in our local mutations to the same `Mutation` component.
|
||||
In this example, we're using the `isBooked` prop passed into the component to determine which mutation we should fire. Just like remote mutations, we can pass in our local mutations to the same `useMutation` hook.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
---
|
||||
title: '7. Update data with mutations'
|
||||
description: Learn how to update data with the Mutation component
|
||||
description: Learn how to update data with the useMutation hook
|
||||
---
|
||||
|
||||
Time to accomplish: _12 Minutes_
|
||||
|
||||
With Apollo Client, updating data from a graph API is as simple as calling a function. Additionally, the Apollo Client cache is smart enough to automatically update in most cases. In this section, we'll learn how to use the `Mutation` component from `react-apollo` to login a user.
|
||||
With Apollo Client, updating data from a graph API is as simple as calling a function. Additionally, the Apollo Client cache is smart enough to automatically update in most cases. In this section, we'll learn how to use the `useMutation` hook to login a user.
|
||||
|
||||
## What is a Mutation component?
|
||||
## What is the useMutation hook?
|
||||
|
||||
The `Mutation` component is another important building block in an Apollo app. It's a React component that provides a function to execute a GraphQL mutation. Additionally, it tracks the loading, completion, and error state of that mutation.
|
||||
The `useMutation` hook is another important building block in an Apollo app. It leverages React's [Hooks API](https://reactjs.org/docs/hooks-intro.html) to provide a function to execute a GraphQL mutation. Additionally, it tracks the loading, completion, and error state of that mutation.
|
||||
|
||||
Updating data with a `Mutation` component from `react-apollo` is very similar to fetching data with a `Query` component. The main difference is that the first argument to the `Mutation` render prop function is a **mutate function** that actually triggers the mutation when it is called. The second argument to the `Mutation` render prop function is a result object that contains loading and error state, as well as the return value from the mutation. Let's see an example:
|
||||
Updating data with a `useMutation` hook from `@apollo/react-hooks` is very similar to fetching data with a `useQuery` hook. The main difference is that the first value in the `useMutation` result tuple is a **mutate function** that actually triggers the mutation when it is called. The second value in the result tuple is a result object that contains loading and error state, as well as the return value from the mutation. Let's see an example:
|
||||
|
||||
## Update data with Mutation
|
||||
## Update data with useMutation
|
||||
|
||||
The first step is defining our GraphQL mutation. To start, navigate to `src/pages/login.js` and copy the code below so we can start building out the login screen:
|
||||
|
||||
@@ -21,7 +21,7 @@ _src/pages/login.js_
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import { Mutation, ApolloConsumer } from 'react-apollo';
|
||||
import { useApolloClient, useMutation } from '@apollo/react-hooks';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { LoginForm, Loading } from '../components';
|
||||
@@ -33,58 +33,48 @@ const LOGIN_USER = gql`
|
||||
`;
|
||||
```
|
||||
|
||||
Just like before, we're using the `gql` function to wrap our GraphQL mutation so it can be parsed into an AST. We're also importing some components that we'll use in the next steps. Now, let's bind this mutation to our component by passing it to the `mutation` prop:
|
||||
Just like before, we're using the `gql` function to wrap our GraphQL mutation so it can be parsed into an AST. We're also importing some components that we'll use in the next steps. Now, let's bind this mutation to our component by passing it to the `useMutation` hook:
|
||||
|
||||
_src/pages/login.js_
|
||||
|
||||
```jsx
|
||||
export default function Login() {
|
||||
return (
|
||||
<Mutation mutation={LOGIN_USER}>
|
||||
{(login, { data }) => <LoginForm login={login} />}
|
||||
</Mutation>
|
||||
);
|
||||
const [login, { data }] = useMutation(LOGIN_USER);
|
||||
return <LoginForm login={login} />;
|
||||
}
|
||||
```
|
||||
|
||||
Our `Mutation` component takes a render prop function as a child that exposes a mutate function (`login`) and the data object returned from the mutation. Finally, we pass our login function to the `LoginForm` component.
|
||||
Our `useMutation` hook returns a mutate function (`login`) and the data object returned from the mutation that we destructure from the tuple. Finally, we pass our login function to the `LoginForm` component.
|
||||
|
||||
To create a better experience for our users, we want to persist the login between sessions. In order to do that, we need to save our login token to `localStorage`. Let's learn how we can use the `onCompleted` handler on `Mutation` to persist our login:
|
||||
To create a better experience for our users, we want to persist the login between sessions. In order to do that, we need to save our login token to `localStorage`. Let's learn how we can use the `onCompleted` handler of `useMutation` to persist our login:
|
||||
|
||||
### Expose Apollo Client with ApolloConsumer
|
||||
### Expose Apollo Client with useApolloClient
|
||||
|
||||
One of the main functions of `react-apollo` is that it puts your `ApolloClient` instance on React's context. Sometimes, we need to access the `ApolloClient` instance to directly call a method that isn't exposed by the `react-apollo` helper components. The `ApolloConsumer` component can help us access the client.
|
||||
One of the main functions of React Apollo is that it puts your `ApolloClient` instance on React's context. Sometimes, we need to access the `ApolloClient` instance to directly call a method that isn't exposed by the `@apollo/react-hooks` helper components. The `useApolloClient` hook can help us access the client.
|
||||
|
||||
`ApolloConsumer` takes a render prop function as a child that is called with the client instance. Let's wrap our `Mutation` component with `ApolloConsumer` to expose the client. Next, we want to pass an `onCompleted` callback to `Mutation` that will be called once the mutation is complete with its return value. This callback is where we will save the login token to `localStorage`.
|
||||
Let's call `useApolloClient` to get the currently configured client instance. Next, we want to pass an `onCompleted` callback to `useMutation` that will be called once the mutation is complete with its return value. This callback is where we will save the login token to `localStorage`.
|
||||
|
||||
In our `onCompleted` handler, we also call `client.writeData` to write local data to the Apollo cache indicating that the user is logged in. This is an example of a **direct write** that we'll explore further in the next section on local state management.
|
||||
|
||||
_src/pages/login.js_
|
||||
|
||||
```jsx{3,4,7-10,22}
|
||||
```jsx{2,6-9}
|
||||
export default function Login() {
|
||||
return (
|
||||
<ApolloConsumer>
|
||||
{client => (
|
||||
<Mutation
|
||||
mutation={LOGIN_USER}
|
||||
onCompleted={({ login }) => {
|
||||
localStorage.setItem('token', login);
|
||||
client.writeData({ data: { isLoggedIn: true } });
|
||||
}}
|
||||
>
|
||||
{(login, { loading, error }) => {
|
||||
// this loading state will probably never show, but it's helpful to
|
||||
// have for testing
|
||||
if (loading) return <Loading />;
|
||||
if (error) return <p>An error occurred</p>;
|
||||
|
||||
return <LoginForm login={login} />;
|
||||
}}
|
||||
</Mutation>
|
||||
)}
|
||||
</ApolloConsumer>
|
||||
const client = useApolloClient();
|
||||
const [login, { loading, error }] = useMutation(
|
||||
LOGIN_USER,
|
||||
{
|
||||
onCompleted({ login }) {
|
||||
localStorage.setItem('token', login);
|
||||
client.writeData({ data: { isLoggedIn: true } });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (loading) return <Loading />;
|
||||
if (error) return <p>An error occurred</p>;
|
||||
|
||||
return <LoginForm login={login} />;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
---
|
||||
title: "6. Fetch data with queries"
|
||||
description: Learn how to fetch data with the Query component
|
||||
description: Learn how to fetch data with the useQuery hook
|
||||
---
|
||||
|
||||
Time to accomplish: _15 Minutes_
|
||||
|
||||
Apollo Client simplifies fetching data from a graph API because it intelligently caches your data, as well as tracks loading and error state. In the previous section, we learned how to fetch a sample query with Apollo Client without using a view integration. In this section, we'll learn how to use the `Query` component from `react-apollo` to fetch more complex queries and execute features like pagination.
|
||||
Apollo Client simplifies fetching data from a graph API because it intelligently caches your data, as well as tracks loading and error state. In the previous section, we learned how to fetch a sample query with Apollo Client without using a view integration. In this section, we'll learn how to use the `useQuery` hook from `@apollo/react-hooks` to fetch more complex queries and execute features like pagination.
|
||||
|
||||
## The Query component
|
||||
## The useQuery hook
|
||||
|
||||
The `Query` component is one of the most important building blocks of an Apollo app. It's a React component that fetches a GraphQL query and exposes the result so you can render your UI based on the data it returns.
|
||||
The `useQuery` hook is one of the most important building blocks of an Apollo app. It's a React Hook that fetches a GraphQL query and exposes the result so you can render your UI based on the data it returns.
|
||||
|
||||
The `Query` component uses the **render prop** pattern to fetch and load data from queries into our UI. The render prop pattern provides the ability to add a function as a child to our `Query` component that will notify React about what you want to render. It exposes the `error`, `loading` and `data` on a result object that is passed into the render prop function. Let's see an example:
|
||||
The `useQuery` hook leverages React's [Hooks API](https://reactjs.org/docs/hooks-intro.html) to fetch and load data from queries into our UI. It exposes `error`, `loading` and `data` properties through a result object, that help us populate and render our component. Let's see an example:
|
||||
|
||||
## Fetching a list
|
||||
|
||||
To create a `Query` component, import `Query` from `react-apollo`, pass your query wrapped with `gql` to `this.props.query`, and provide a render prop function to `this.props.children` that uses the `loading`, `data`, and `error` properties on the result object to render UI in your app.
|
||||
To create a component with `useQuery`, import `useQuery` from `@apollo/react-hooks`, pass your query wrapped with `gql` in as the first parameter, then wire your component up to use the `loading`, `data`, and `error` properties on the result object to render UI in your app.
|
||||
|
||||
First, we're going to build a GraphQL query that fetches a list of launches. We're also going to import some components that we will need in the next step. Navigate to `src/pages/launches.js` to get started and copy the code below into the file.
|
||||
|
||||
@@ -23,7 +23,7 @@ _src/pages/launches.js_
|
||||
|
||||
```js
|
||||
import React, { Fragment } from 'react';
|
||||
import { Query } from 'react-apollo';
|
||||
import { useQuery } from '@apollo/react-hooks';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { LaunchTile, Header, Button, Loading } from '../components';
|
||||
@@ -52,38 +52,33 @@ const GET_LAUNCHES = gql`
|
||||
|
||||
Here, we're defining a query to fetch a list of launches by calling the `launches` query from our schema. The `launches` query returns an object type with a list of launches, in addition to the `cursor` of the paginated list and whether or not the list `hasMore` launches. We need to wrap the query with the `gql` function in order to parse it into an AST.
|
||||
|
||||
Now, let's pass that query to Apollo's `Query` component to render the list:
|
||||
Now, let's pass that query to Apollo's `useQuery` component to render the list:
|
||||
|
||||
_src/pages/launches.js_
|
||||
|
||||
```jsx
|
||||
export default function Launches() {
|
||||
return (
|
||||
<Query query={GET_LAUNCHES}>
|
||||
{({ data, loading, error }) => {
|
||||
if (loading) return <Loading />;
|
||||
if (error) return <p>ERROR</p>;
|
||||
const { data, loading, error } = useQuery(GET_LAUNCHES);
|
||||
if (loading) return <Loading />;
|
||||
if (error) return <p>ERROR</p>;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Header />
|
||||
{data.launches &&
|
||||
data.launches.launches &&
|
||||
data.launches.launches.map(launch => (
|
||||
<LaunchTile
|
||||
key={launch.id}
|
||||
launch={launch}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
);
|
||||
}}
|
||||
</Query>
|
||||
return (
|
||||
<Fragment>
|
||||
<Header />
|
||||
{data.launches &&
|
||||
data.launches.launches &&
|
||||
data.launches.launches.map(launch => (
|
||||
<LaunchTile
|
||||
key={launch.id}
|
||||
launch={launch}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
To render the list, we pass the `GET_LAUNCHES` query from the previous step into our `Query` component. We then define a render prop function as the child of `Query` that's called with the state of our query (`loading`, `error`, and `data`). Depending on the state, we either render a loading indicator, an error message, or a list of launches.
|
||||
To render the list, we pass the `GET_LAUNCHES` query from the previous step into our `useQuery` hook. Then, depending on the state of `loading`, `error`, and `data`, we either render a loading indicator, an error message, or a list of launches.
|
||||
|
||||
We're not done yet! Right now, this query is only fetching the first 20 launches from the list. To fetch the full list of launches, we need to build a pagination feature that displays a `Load More` button for loading more items on the screen. Let's learn how!
|
||||
|
||||
@@ -91,20 +86,17 @@ We're not done yet! Right now, this query is only fetching the first 20 launches
|
||||
|
||||
Apollo Client has built-in helpers to make adding pagination to our app much easier than it would be if we were writing the logic ourselves.
|
||||
|
||||
To build a paginated list with Apollo, we first need to destructure the `fetchMore` function from the `Query` render prop function.
|
||||
To build a paginated list with Apollo, we first need to destructure the `fetchMore` function from the `useQuery` result object:
|
||||
|
||||
_src/pages/launches.js_
|
||||
|
||||
```jsx
|
||||
export default function Launches() {
|
||||
const { data, loading, error, fetchMore } = useQuery(GET_LAUNCHES); // highlight-line
|
||||
return (
|
||||
<Query query={GET_LAUNCHES}>
|
||||
{({ data, loading, error, fetchMore }) => { // highlight-line
|
||||
// same as above
|
||||
}}
|
||||
</Query>
|
||||
// same as above
|
||||
);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Now that we have `fetchMore`, let's connect it to a Load More button to fetch more items when it's clicked. To do this, we will need to specify an `updateQuery` function on the return object from `fetchMore` that tells the Apollo cache how to update our query with the new items we're fetching.
|
||||
@@ -158,7 +150,7 @@ _src/pages/launch.js_
|
||||
|
||||
```jsx
|
||||
import React, { Fragment } from 'react';
|
||||
import { Query } from 'react-apollo';
|
||||
import { useQuery } from '@apollo/react-hooks';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import Loading from '../components/loading';
|
||||
@@ -186,29 +178,27 @@ export const GET_LAUNCH_DETAILS = gql`
|
||||
`;
|
||||
```
|
||||
|
||||
Now that we have a query, let's render a `Query` component to execute it. This time, we'll also need to pass in the `launchId` as a variable to the query, which we'll do by adding a `variables` prop to `Query`. The `launchId` comes through as a prop from the router.
|
||||
Now that we have a query, let's render a component with `useQuery` to execute it. This time, we'll also need to pass in the `launchId` as a variable to the query, which we'll do by adding a `variables` option to `useQuery`. The `launchId` comes through as a prop from the router.
|
||||
|
||||
_src/pages/launch.js_
|
||||
|
||||
```jsx
|
||||
export default function Launch({ launchId }) {
|
||||
return (
|
||||
<Query query={GET_LAUNCH_DETAILS} variables={{ launchId }}>
|
||||
{({ data, loading, error }) => {
|
||||
if (loading) return <Loading />;
|
||||
if (error) return <p>ERROR: {error.message}</p>;
|
||||
const { data, loading, error } = useQuery(
|
||||
GET_LAUNCH_DETAILS,
|
||||
{ variables: { launchId } }
|
||||
);
|
||||
if (loading) return <Loading />;
|
||||
if (error) return <p>ERROR: {error.message}</p>;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Header image={data.launch.mission.missionPatch}>
|
||||
{data.launch.mission.name}
|
||||
</Header>
|
||||
<LaunchDetail {...data.launch} />
|
||||
<ActionButton {...data.launch} />
|
||||
</Fragment>
|
||||
);
|
||||
}}
|
||||
</Query>
|
||||
return (
|
||||
<Fragment>
|
||||
<Header image={data.launch.mission.missionPatch}>
|
||||
{data.launch.mission.name}
|
||||
</Header>
|
||||
<LaunchDetail {...data.launch} />
|
||||
<ActionButton {...data.launch} />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
```
|
||||
@@ -284,7 +274,7 @@ Great, now we've successfully refactored our queries to use fragments. Fragments
|
||||
|
||||
### Customizing the fetch policy
|
||||
|
||||
Sometimes, it's useful to tell Apollo Client to bypass the cache altogether if you have some data that constantly needs to be refreshed. We can do this by customizing the `Query` component's `fetchPolicy`.
|
||||
Sometimes, it's useful to tell Apollo Client to bypass the cache altogether if you have some data that constantly needs to be refreshed. We can do this by customizing the `useQuery` hook's `fetchPolicy`.
|
||||
|
||||
First, let's navigate to `src/pages/profile.js` and write our query:
|
||||
|
||||
@@ -292,7 +282,7 @@ _src/pages/profile.js_
|
||||
|
||||
```js
|
||||
import React, { Fragment } from 'react';
|
||||
import { Query } from 'react-apollo';
|
||||
import { useQuery } from '@apollo/react-hooks';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { Loading, Header, LaunchTile } from '../components';
|
||||
@@ -312,36 +302,35 @@ const GET_MY_TRIPS = gql`
|
||||
`;
|
||||
```
|
||||
|
||||
Next, let's render a `Query` component to fetch a logged in user's list of trips. By default, Apollo Client's fetch policy is `cache-first`, which means it checks the cache to see if the result is there before making a network request. Since we want this list to always reflect the newest data from our graph API, we set the `fetchPolicy` for this query to `network-only`:
|
||||
Next, let's render a component with `useQuery` to fetch a logged in user's list of trips. By default, Apollo Client's fetch policy is `cache-first`, which means it checks the cache to see if the result is there before making a network request. Since we want this list to always reflect the newest data from our graph API, we set the `fetchPolicy` for this query to `network-only`:
|
||||
|
||||
_src/pages/profile.js_
|
||||
|
||||
```jsx
|
||||
export default function Profile() {
|
||||
return (
|
||||
<Query query={GET_MY_TRIPS} fetchPolicy="network-only"> {/* highlight-line */}
|
||||
{({ data, loading, error }) => {
|
||||
if (loading) return <Loading />;
|
||||
if (error) return <p>ERROR: {error.message}</p>;
|
||||
const { data, loading, error } = useQuery(
|
||||
GET_MY_TRIPS,
|
||||
{ fetchPolicy: "network-only" } // highlight-line
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Header>My Trips</Header>
|
||||
{data.me && data.me.trips.length ? (
|
||||
data.me.trips.map(launch => (
|
||||
<LaunchTile key={launch.id} launch={launch} />
|
||||
))
|
||||
) : (
|
||||
<p>You haven't booked any trips</p>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}}
|
||||
</Query>
|
||||
if (loading) return <Loading />;
|
||||
if (error) return <p>ERROR: {error.message}</p>;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Header>My Trips</Header>
|
||||
{data.me && data.me.trips.length ? (
|
||||
data.me.trips.map(launch => (
|
||||
<LaunchTile key={launch.id} launch={launch} />
|
||||
))
|
||||
) : (
|
||||
<p>You haven't booked any trips</p>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
If you try to render this query, you'll notice that it returns null. This is because we need to implement our login feature first. We're going to tackle login in the next section.
|
||||
|
||||
Now that we've learned how to build `Query` components that can fetch a paginated list, share fragments, and customize the fetch policy, it's time to progress to the next section so we can learn how to update data with mutations!
|
||||
Now that we've learned how to leverage `useQuery` to build components that can fetch a paginated list, share fragments, and customize the fetch policy, it's time to progress to the next section so we can learn how to update data with mutations!
|
||||
|
||||
Reference in New Issue
Block a user