Odyssey

Client-side GraphQL with React & Apollo
beta

Feature overview and setupGraphQL basicsApollo ExplorerApollo ClientCodegenDefining a queryArgumentsThe useQuery hook with variablesIntroducing mutationsOur mutation in action
6. Defining a query
5m

Overview

Now that we've initialized Apollo Client and taken care of generating our types, we can give our client its first query to execute.

In this lesson, we will:

  • Provide our React component with a GraphQL query to execute
  • Handle loading, error, and render states for our response data

📦 A query in a component

The code for our tracks page lives in src/pages/tracks.tsx. At the moment, the page just displays the bare layout that we've seen previously. Let's add a query definition to it.

Just like when we defined our schema, we need to wrap all GraphQL strings in the gql function. Let's import gql:

src/pages/tracks.tsx
import { gql } from "../__generated__/";

Next we'll declare a constant called GET_TRACKS with an empty GraphQL string (by convention, query constants are in ALL_CAPS):

const GET_TRACKS = gql(`
# Query goes here
`);

Note: We use the gql import from our __generated__/index.ts file as a function, with parentheses wrapping the backticks and operation!

Now, remember the query we built in the Apollo Explorer to retrieve track data? Conveniently, that's exactly the query we need!

Head back to the Explorer, where we'll access the query from our Sandbox operation collection.

http://studio.apollographql.com/sandbox/explorer

Opening the Operation Collections panel to access a saved operation.

When we click on TracksForHome from our collection, the saved query is automatically inserted into a new tab in the Operation panel.

http://studio.apollographql.com/sandbox/explorer

Clicking on an operation saved in a collection to insert it into the Operation panel.

Let's copy the query, and return to our code.

We can now paste the query directly into our empty gql function.

/** GET_TRACKS query to retrieve all tracks */
const GET_TRACKS = gql(`
query GetTracks {
tracksForHome {
id
title
thumbnail
length
modulesCount
author {
id
name
photo
}
}
}
`);

Now that our frontend code contains an actual GraphQL operation, we can run our npm run generate function again and let the GraphQL Code Generator scan and anticipate the operations that our app will be sending. It will use this information to determine the TypeScript types for our operations. Run the generate command:

npm run generate

Great! Now our generated types understand what kind of query we're going to send, and what kind of data we expect to get back.

Our query is ready to execute. Let's finally display some catstronauts on our homepage!

📡 Executing with useQuery

To execute queries, we'll use Apollo Client's useQuery hook.

The useQuery hook takes in GraphQL query string as an argument.

When our component renders, useQuery returns an object that contains loading, error, and data properties that we can use to render our UI. Let's put all of that into code.

Note: Check out the official Apollo docs on the useQuery hook to learn more about this function.

First, we need to import useQuery from the @apollo/client package :

tracks.tsx
import { gql } from "../__generated__";
import { useQuery } from "@apollo/client";

Now, in our Tracks functional component (below the opened curly brace), we'll declare three destructured constants from our useQuery hook: loading, error, and data. We call useQuery with our GET_TRACKS query as its argument:

const { loading, error, data } = useQuery(GET_TRACKS);

Below that, we'll first use the loading constant:

if (loading) return "Loading...";

As long as loading is true (indicating the query is still in flight), the component will just render a Loading... message.

When loading is false, the query is complete. This means we either have data, or we have an error.

Let's add another conditional statement that handles the error state:

if (error) return `Error! ${error.message}`;

If we don't have an error, we must have data! For now, we'll just dump our raw data object with JSON.stringify to see what happens.

<Layout grid>{JSON.stringify(data)}</Layout>

With all of that added, here's what the completed Tracks component looks like. Make sure yours matches!

const Tracks = () => {
const { loading, error, data } = useQuery(GET_TRACKS);
if (loading) return "Loading...";
if (error) return `Error! ${error.message}`;
return <Layout grid>{JSON.stringify(data)}</Layout>;
};

Let's restart our app. We first see the loading message, then a raw JSON response. The response includes a tracksForHome object (the name of our operation), which contains an array of Track objects. Looks good so far! Now, let's use this data in an actual view.

http://localhost:3000

The JSON response to our query, outputted directly into the UI

Rendering TrackCards

Conveniently, we already have a TrackCard component that's ready to go. We'll need to import the component and feed the response data to it, but first let's open /src/containers/track-card.tsx to see how it works.

/**
* Track Card component renders basic info in a card format
* for each track populating the tracks grid homepage.
*/
const TrackCard: React.FC<{ track: any }> = ({ track }) => {
const { title, thumbnail, author, length, modulesCount, id } = track;
//...
};

Right away we can see that the TrackCard component accepts a prop called track, but right now its type is any. Now that we've generated types from our GraphQL server, we can fix this and more accurately describe the type of data the track prop should provide.

At the top of the file, let's import the Track type that exists in our __generated__ folder's graphql file.

src/containers/track-card.tsx
import type { Track } from '../__generated__/graphql'

We can use this Track type to set the track prop's data type, replacing any.

/**
* Track Card component renders basic info in a card format
* for each track populating the tracks grid homepage.
*/
const TrackCard: React.FC<{ track: Track }> = ({ track }) => {
const { title, thumbnail, author, length, modulesCount, id } = track;
//...
};

Now if you hover over the Track type we just added, you'll see the breakdown of the exact type that we defined in our backend schema. We know exactly the details that are available for us to use on data of this type! Let's break them down.

The component takes a track prop and uses its title, thumbnail, author, length, modulesCount, and id. So, we just need to pass each TrackCard a Track object from our query response.

Let's head back to src/pages/tracks.tsx. We've seen that the server response to our GET_TRACKS GraphQL query includes a tracksForHome key, which contains the array of tracks.

First, let's import the TrackCard component.

pages/tracks.tsx
import TrackCard from "../containers/track-card";

To create one card per track, we'll map through the tracksForHome array and return a TrackCard component with its corresponding track data as its prop:

<Layout grid>
{data?.tracksForHome?.map((track) => (
<TrackCard key={track.id} track={track} />
))}
</Layout>

Right away, we'll see that there's an error on the track property!


For context, let's take a look at the Track type that the GraphQL Code Generator generated for us.

/** A track is a group of Modules that teaches about a specific topic */
export type Track = {
__typename?: 'Track';
/** The track's main Author */
author: Author;
/** The track's complete description, can be in markdown format */
description?: Maybe<Scalars['String']['output']>;
id: Scalars['ID']['output'];
/** The track's approximate length to complete, in minutes */
length?: Maybe<Scalars['Int']['output']>;
/** The track's complete array of Modules */
modules: Array<Module>;
/** The number of modules this track contains */
modulesCount?: Maybe<Scalars['Int']['output']>;
/** The number of times a track has been viewed */
numberOfViews?: Maybe<Scalars['Int']['output']>;
/** The track's illustration to display in track card or track page detail */
thumbnail?: Maybe<Scalars['String']['output']>;
/** The track's title */
title: Scalars['String']['output'];
};

Here we can see that modules is not an optional property on our Track type. Because the query we're making inside of this component does not include modules details, TypeScript is letting us know that we're violating one of the rules of this Track type.


To fix this, we'll jump back into containers/track-card.tsx. Here we'll update the type signature of the TrackCard to omit modules from the properties it requires on the Track type we pass it.


const TrackCard: React.FC<{ track: Omit<Track, "modules"> }> = ({ track }) => {
const { title, thumbnail, author, length, modulesCount, id } = track;
// ... TrackCard body
}

This allows us to pass the track property an object that mostly adheres to the Track TypeScript type—just without its modules!

We refresh our browser, and voila! We get a bunch of nice-looking cards with cool catstronaut thumbnails. Our track title, length, number of modules, and author information all display nicely thanks to our TrackCard component. Pretty neat!

http://localhost:3000

The UI of our Catstronauts app, displaying a number of track cards

Wrapping query results

While refreshing the browser, you might have noticed that because we return the loading message as a simple string, we don't currently show the component's entire layout and navbar while it's loading (the same issue goes for the error message). We should make sure that our UI's behavior is consistent throughout all of a query's phases.

That's where our QueryResult helper component comes in. This isn't a component that's provided directly by an Apollo library. We've added it to use query results in a consistent, predictable way throughout our app.

Let's open components/query-result. This component takes the useQuery hook's return values as props. It then performs basic conditional logic to either render a spinner, an error message, or its children:

components/query-result.tsx
const QueryResult: React.FC<PropsWithChildren<QueryResultProps>> = ({
loading,
error,
data,
children,
}): React.ReactElement<any, any> | null => {
if (error) {
return <p>ERROR: {error.message}</p>;
}
if (loading) {
return (
<SpinnerContainer>
<LoadingSpinner data-testid="spinner" size="large" theme="grayscale" />
</SpinnerContainer>
);
}
if (data) {
return <>{children}</>;
}
return <p>Nothing to show...</p>;
};

Back to our tracks.tsx file, we'll import QueryResult at the top:

import QueryResult from "../components/query-result";

We can now remove the lines in this file that handle the loading and error states, because the QueryResult component will handle them instead.

const Tracks = () => {
const { loading, error, data } = useQuery(GET_TRACKS);
- if (loading) return "Loading...";
- if (error) return `Error! ${error.message}`;
return (
<Layout grid>
{data?.tracksForHome?.map((track) => (
<TrackCard key={track.id} track={track} />
))}
</Layout>
);
};

We wrap QueryResult around our map function and give it the props it needs:

return (
<Layout grid>
<QueryResult error={error} loading={loading} data={data}>
{data?.tracksForHome?.map((track) => (
<TrackCard key={track.id} track={track} />
))}
</QueryResult>
</Layout>
);

Refreshing our browser, we get a nice spinner while loading, and then our cards appear!

http://localhost:3000

The UI of our Catstronauts app, displaying a number of track cards

After all that code, the tracks.tsx file should look like this:

import React from "react";
import { Layout, QueryResult } from "../components";
import { gql } from "../__generated__";
import { useQuery } from "@apollo/client";
import TrackCard from "../containers/track-card";
/** GET_TRACKS query to retrieve all tracks */
const GET_TRACKS = gql(`
query GetTracks {
tracksForHome {
id
title
thumbnail
length
modulesCount
author {
id
name
photo
}
}
}
`);
/**
* Tracks Page is the Catstronauts home page.
* We display a grid of tracks fetched with useQuery with the GET_TRACKS query
*/
const Tracks = () => {
const { loading, error, data } = useQuery(GET_TRACKS);
return (
<Layout grid>
<QueryResult error={error} loading={loading} data={data}>
{data?.tracksForHome?.map((track) => (
<TrackCard key={track.id} track={track} />
))}
</QueryResult>
</Layout>
);
};
export default Tracks;

And there you have it! Our homepage is populated with a cool grid of track cards, as laid out in our initial mock-up.

Task!

Practice

Code Challenge!

Create a ListSpaceCats query with a spaceCats query field and its name, age and missions selection set. For the missions field, select name and description

Which of the following are best practices when creating client queries?
Code Challenge!

Use the useQuery hook with the SPACECATS query and destructure the loading, error and data properties from the result.

Loading...
Loading editor
What is the useQuery hook used for?

Key takeaways

  • The useQuery hook is the primary API for executing queries in a React application.
  • The useQuery hook returns an object that contains loading, error, and data properties that we can use to determine the elements in our UI.

Up next

Our homepage looks good, but we've got nowhere to go from here: up next, let's explore how we can set up our app to show details for just one track object.

Previous
Next

Share your questions and comments about this lesson

This course is currently in

beta
. Your feedback helps us improve! If you're stuck or confused, let us know and we'll help you out. All comments are public and must follow the Apollo Code of Conduct. Note that comments that have been resolved or addressed may be removed.

You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.

              Apollo Client

              An open-source library for client-side state management and GraphQL operation handling in Javascript/Typescript. Apollo Client is a fully featured caching GraphQL client with integrations for React, Angular, and more.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              operation

              A single query, mutation, or subscription that clients send to a GraphQL server to request or manipulate data.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              operation

              A single query, mutation, or subscription that clients send to a GraphQL server to request or manipulate data.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              operation

              A single query, mutation, or subscription that clients send to a GraphQL server to request or manipulate data.

              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              operations

              A single query, mutation, or subscription that clients send to a GraphQL server to request or manipulate data.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              Apollo Client

              An open-source library for client-side state management and GraphQL operation handling in Javascript/Typescript. Apollo Client is a fully featured caching GraphQL client with integrations for React, Angular, and more.

              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              argument

              A key-value pair associated with a particular schema field that lets operations pass data to that field's resolver.

              Argument values can be hardcoded as literal values (shown below for clarity) or provided via GraphQL variables (recommended).

              query GetHuman {
              human(id: "200") {
              name
              height(unit: "meters")
              }
              }
              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              argument

              A key-value pair associated with a particular schema field that lets operations pass data to that field's resolver.

              Argument values can be hardcoded as literal values (shown below for clarity) or provided via GraphQL variables (recommended).

              query GetHuman {
              human(id: "200") {
              name
              height(unit: "meters")
              }
              }
              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              operation

              A single query, mutation, or subscription that clients send to a GraphQL server to request or manipulate data.

              GraphQL server

              A server that contains a GraphQL schema and can resolve client-requested operations that are executed against that schema.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              GraphQL

              An open-source query language and specification for APIs that enables clients to request specific data, promoting efficiency and flexibility in data retrieval.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              query

              A request for specific data from a GraphQL server. Clients define the structure of the response, enabling precise and efficient data retrieval.

              NEW COURSE ALERT

              Introducing Apollo Connectors

              Connectors are the new and easy way to get started with GraphQL, using existing REST APIs.

              Say goodbye to GraphQL servers and resolvers—now, everything happens in the schema!

              Take the course