Build a Paginated List and Fetch Details with Apollo Client
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 restructure the fetchMore function from the useQuery result object:
// src/pages/launches.jsx
const Launches = () => {
const {
data,
loading,
error,
fetchMore} = useQuery(GET_LAUNCHES);
// 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.
To do this, copy the code below and add it before the closing </Fragment> tag in the Launches component we added in the previous step.
// src/pages/launches.jsx
{data.launches &&
data.launches.hasMore && (
<Button
onClick={() =>
fetchMore({variables: {
after: data.launches.cursor,
},
updateQuery: (prev, { fetchMoreResult, ...rest }) => {if (!fetchMoreResult) return prev;
return {
...fetchMoreResult,
launches: {
...fetchMoreResult.launches,
launches: [
...prev.launches.launches,
...fetchMoreResult.launches.launches,
],
},
};
},
})
}
>
Load More
</Button>
)
}
First, we check to see if we have more launches available in our query. If we do, we render a button with a click handler that calls the fetchMore function from Apollo. The fetchMore function receives new variables for the list of launches query, which is represented by our cursor.
We also define the updateQuery function to tell Apollo how to update the list of launches in the cache. To do this, we take the previous query result and combine it with the new query result from fetchMore.
In the next step, we'll learn how to wire up the launch detail page to display a single launch when an item in the list is clicked.
Fetching a single launch
Let's navigate to src/pages/launch.jsx to build out our detail page. First, we should import some components and define our GraphQL query to get the launch details.
// src/pages/launch.jsx
import React, { Fragment } from "react";
import { useQuery } from "@apollo/react-hooks";
import gql from "graphql-tag";
import {Loading, Header, LaunchDetail} from "../components";
import {ActionButton} from "../containers";
export const GET_LAUNCH_DETAILS = gql`
query LaunchDetails($launchId: ID!) {
launch(id: $launchId) {
id
site
isBooked
rocket {
id
name
type
}
mission {
name
missionPatch
}
}
}
`;
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.jsx
const Launch = ({ launchId }) => {
const { data, loading, error } = useQuery(GET_LAUNCH_DETAILS, {
variables: { launchId }
});
if (loading) return <Loading />;
if (error) return <p>ERROR: {error.message}</p>;
if (!data) return <p>Not found</p>;
return (
<Fragment>
<Header
image={
data.launch && data.launch.mission && data.launch.mission.missionPatch
}
>
{data && data.launch && data.launch.mission && data.launch.mission.name}
</Header>
<LaunchDetail {...data.launch} />
<ActionButton {...data.launch} />
</Fragment>
);
};
export default Launch;
Just like before, we use the status of the query to render either a loading or error state, or data when the query completes.
Using fragments to share code
You may have noticed that the queries for fetching a list of launches and fetching a launch detail share a lot of the same fields. When we have two GraphQL operations that contain the same fields, we can use a fragment to share fields between the two.
To learn how to build a fragment, navigate to src/pages/launches.jsx and copy the code below into the file:
src/pages/launches.jsx
export const LAUNCH_TILE_DATA = gql`
fragment LaunchTile on Launch {
id
isBooked
rocket {
id
name
}
mission {
name
missionPatch
}
}
`;
We define a GraphQL fragment by giving it a name (LaunchTile) and defining it on a type on our schema (Launch). The name we give our fragment can be anything, but the type must correspond to a type in our schema.
To use our fragment in our query, we import it into the GraphQL document and use the spread operator to spread the fields into our query:
src/pages/launches.jsx
const GET_LAUNCHES = gql`
query launchList($after: String) {
launches(after: $after) {
cursor
hasMore
launches {
...LaunchTile }
}
}
${LAUNCH_TILE_DATA}
`;
Let's use our fragment in our launch detail query too. Be sure to import the fragment from the launches page before you use it:
src/pages/launch.jsx
import { LAUNCH_TILE_DATA } from './launches';
export const GET_LAUNCH_DETAILS = gql`
query LaunchDetails($launchId: ID!) {
launch(id: $launchId) {
site
rocket {
type
}
...LaunchTile }
}
${LAUNCH_TILE_DATA}
`;
Great, now we've successfully refactored our queries to use fragments. Fragments are a helpful tool that you'll use a lot as you're building GraphQL queries and mutations.
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 useQuery hook's fetchPolicy.
First, let's navigate to src/pages/profile.jsx and write our query:
// src/pages/profile.jsx
import React, { Fragment } from "react";
import { useQuery } from "@apollo/react-hooks";
import gql from "graphql-tag";
import { Loading, Header, LaunchTile } from "../components";
import { LAUNCH_TILE_DATA } from "./launches";
export const GET_MY_TRIPS = gql`
query GetMyTrips {
me {
id
email
trips {
...LaunchTile
}
}
}
${LAUNCH_TILE_DATA}
`;
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.jsx
const Profile = () => {
const { data, loading, error } = useQuery(
GET_MY_TRIPS,
{ fetchPolicy: "network-only" } );
if (loading) return <Loading />;
if (error) return <p>ERROR: {error.message}</p>;
if (data === undefined) return <p>ERROR</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>
);
};
export default Profile;
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 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!
Previous:
Update Data with Mutations in Apollo Client.
Next:
Authenticate users in GraphQL with Apollo Server.
It will be nice if you may share this link in any developer community or anywhere else, from where other developers may find this content. Thanks.
https://w3resource.com/apollo-graphql/build-a-paginated-list.php
- Weekly Trends and Language Statistics
- Weekly Trends and Language Statistics