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
8. The useQuery hook with variables
4m

Overview

Time to take care of that UI and bring our track details page to life.

In this lesson, we will:

  • Build out the track page
  • Bring our new GET_TRACK operation into the frontend
  • Call the useQuery hook, passing a trackId variable

Accessing the track page

Our app's already set up with the routing logic to display a page for a specific track. Let's check it out.

Run npm start.

This should open up a page in the browser to http://127.0.0.1:3000/, or localhost:3000, to show the homepage.

Then, let's navigate to: localhost:3000/track/c_0. We should still see an empty Catstronauts layout, but this tells us the route is working. We'll start to fill this in with the data we retrieve from the query.

Screenshot showing a blank layout on the /track/c_0 page
Task!

If you open up the src/pages/index.tsx file, you'll find the routing logic for our application.

Inside the Routes component, there are two instances of Route:

  1. The first is for the homepage (/) and renders the Tracks component.
  2. The second renders the details for a specific Track, which it determines from the trackId param passed in the route, /track/:trackId.
<Routes>
<Route element={<Tracks />} path="/" />
<Route element={<Track />} path="/track/:trackId" />
</Routes>

You can learn more about how this routing works in the React Router docs. For now, we know that if we go to this path, or URL, in our browser, and give it a trackId like c_0 for example, it will display the Track page.

📄 Building out the track page

Let's give this track page something to display!

Jump into the src/pages folder, and open up track.tsx.

This file exports a basic component called Track, but there's not much going on here yet.

import React from "react";
import { Layout, QueryResult } from "../components";
const Track = () => {
return <Layout></Layout>;
};
export default Track;

Taking care of imports

Let's bring in some of the packages we'll use to build out this page.

First, we'll import gql from our __generated__ folder, and useQuery from @apollo/client. Then, to determine which track we'll display details for, we need to get access to the trackId passed in the route. We'll use useParams from react-router-dom for this.

src/pages/track.tsx
import { gql } from "../__generated__";
import { useQuery } from "@apollo/client";
import { useParams } from "react-router-dom";

Now let's jump down to the Track component. Just inside the curly braces, we'll destructure trackId from the object returned by the useParams function. If there's no trackId passed, we'll set it to be an empty string.

const Track = () => {
const { trackId = "" } = useParams();
return <Layout></Layout>;
};

💻 Setting up our client's query

Time to build our track query. We'll call it GET_TRACK all caps, and use the gql function from our __generated__ folder.

src/pages/track.tsx
export const GET_TRACK = gql(`
# our query goes here
`);

And now we could either build our query by hand, or, because we already did the job in Sandbox, let's head back there, copy the query in our Operation panel and paste it in our GET_TRACK variable just between the backticks in the gql tag.

query GetTrack($trackId: ID!) {
track(id: $trackId) {
id
title
author {
id
name
photo
}
thumbnail
length
modulesCount
description
numberOfViews
modules {
id
title
length
content
videoUrl
}
}
}

Rerunning codegen

We've written a new GraphQL operation for the frontend; in order to keep our TypeScript code accurate, we need to run the codegen command in our root folder again! This will reassess the operations being used in our frontend code, and automatically generate the types needed to keep our code consistent and bug-free. Anytime we update our GraphQL operations, or add or delete an operation, we should make sure that we regenerate our types!

npm run generate

We should see some happy output that our codegen ran successfully, and we're good to proceed!

🪝 Setting up the useQuery hook

As you'll remember from an earlier lesson, we use Apollo Client's useQuery hook to make a call from our client to the GraphQL server.

We'll be using this hook inside the Track component. Before the return line, we can declare our usual loading, error and data object that we'll receive from our useQuery hook.

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

We pass the GET_TRACK query as the hook's first argument, and now the big difference from our previous query is the addition of a second argument: an options object.

This object will hold a variables key, note variables, with an "S" because it can have multiple variables. This variables key takes an object as a value, and here is where we'll pass our trackId.

const { loading, error, data } = useQuery(GET_TRACK, {
variables: { trackId },
});

The QueryResult component

Similar to the homepage, we'll use the pre-built QueryResult component to handle any errors and display the loading state properly.

Add the QueryResult component within the Layout component's opening and closing tags:

<Layout>
<QueryResult error={error} loading={loading} data={data}>
{/* this is where our component displaying the data will go */}
</QueryResult>
</Layout>

When the query is finished loading and there are no errors, the QueryResult component will render its children, passing them the data they need.

The TrackDetail component

We have conveniently provided a TrackDetail component, ready to use to display that data. It's located in the src/components folder, so feel free to take a minute to look at it and see how the UI elements are organized if you're curious.

Let's import the TrackDetail component at the top of our track.tsx file.

import TrackDetail from "../components/track-detail";

Now inside QueryResult, we can render the TrackDetail component and set the track prop to data?.track, using optional chaining here since the data won't be available until the query is finished loading.

<TrackDetail track={data?.track} />

And we're good for the track page! Here's what the track.tsx file should look like after all our changes:

src/pages/track.tsx
import React from "react";
import { gql } from "../__generated__";
import { useQuery } from "@apollo/client";
import { Layout, QueryResult } from "../components";
import { useParams } from "react-router-dom";
import TrackDetail from "../components/track-detail";
export const GET_TRACK = gql(`
query GetTrack($trackId: ID!) {
track(id: $trackId) {
id
title
author {
id
name
photo
}
thumbnail
length
modulesCount
numberOfViews
modules {
id
title
length
}
description
}
}
`);
const Track = () => {
const { trackId = "" } = useParams();
const { loading, error, data } = useQuery(GET_TRACK, {
variables: { trackId },
});
return (
<Layout>
<QueryResult error={error} loading={loading} data={data}>
<TrackDetail track={data?.track} />
</QueryResult>
</Layout>
);
};
export default Track;

💻 Browser check!

If we navigate back to the browser to localhost:3000/track/c_0, we should see the track page with all its details showing up! We see the nice large thumbnail of our space kitties, the title, track details, author, module details, and description below! If we change the URL to show different track IDs, such as c_1 or c_2, the page updates with the correct data.

Great, we're on the right track! 🥁

Screenshot showing a track page with all of its details

Let's head back to the homepage at localhost:3000.

We still have our homepage with our tracks card grid, so we didn't break anything there. Now if we click a card, we go to that track's page.

Awesome! We can go back to the homepage, click on a few other tracks and see that their data is loading properly as well.

😲 Behind the scenes caching

Now you might notice that if we click on a track we already clicked on before, the page pops up super fast! Compared to clicking on a track that we've never clicked on before, we can see the loading icon spinning before we see any detail on the page.

This fast-loading behavior is thanks to Apollo Client!

The first time we send a query to the GraphQL server, Apollo Client stores the results in the cache. The next time we try to send that same query (for example, navigating to the same page again), it will load the results from the cache, instead of sending unnecessary calls across the network.

Pretty handy! Apollo Client takes care of this caching behavior for us with the InMemoryCache we set up in an earlier lesson.

Practice

Query from the client
We wrap our query string in the 
 
 function and then send it to our server with the 
 
 hook.

Drag items from this box to the blanks above

  • useState

  • useQuery

  • gql

  • graphql

  • useApolloClient

To pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item. Press space again to drop the item in its new position, or press escape to cancel.
Can you navigate to the correct track page from the homepage? What's the last module on the track 'Cat-strophysics, master class'?
Code Challenge!

Use the useQuery hook to send the GET_SPACECAT query to the server. It takes a spaceCatId as a variable. Destructure the loading, error and data properties from the return object of the hook.

The useQuery hook
The useQuery hook returns an object with three useful properties that we use in our app: 
 
 indicates whether the query has completed and results have been returned. 
 
 is an object that contains any errors that the operation has thrown.
 
 contains the results of the query after it has completed. To set 
 
 in our query, we declare them in the 
 
parameter of the useQuery hook, inside an options object.

Drag items from this box to the blanks above

  • error

  • first

  • variables

  • isComplete

  • arguments

  • loading

  • data

  • results

  • second

To pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item. Press space again to drop the item in its new position, or press escape to cancel.

Key takeaways

  • The useQuery hook takes an optional second argument, an options object where we can define an object of variables.
  • The Apollo Client stores query results in its cache, which allows for faster content loading.

Up next

Our app is set as far as querying for data goes, but what about changing data? To bring some interactivity to our application, we'll dive into our final topic for this course: GraphQL mutations.

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.

              operation

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

              variable

              A placeholder for dynamic values in an operation allowing parameterization and reusability in requests. Variables can be used to fill arguments or passed to directives.

              query GetUser($userId: ID!) {
              user(id: $userId) {
              firstName
              }
              }

              In the query above, userId is a variable. The variable and its type are declared in the operation signature, signified by a $. The type of variable is a non-nullable ID. A variable's type must match the type of any argument it's used for.

              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.

              variable

              A placeholder for dynamic values in an operation allowing parameterization and reusability in requests. Variables can be used to fill arguments or passed to directives.

              query GetUser($userId: ID!) {
              user(id: $userId) {
              firstName
              }
              }

              In the query above, userId is a variable. The variable and its type are declared in the operation signature, signified by a $. The type of variable is a non-nullable ID. A variable's type must match the type of any argument it's used for.

              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.

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

              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")
              }
              }
              variables

              A placeholder for dynamic values in an operation allowing parameterization and reusability in requests. Variables can be used to fill arguments or passed to directives.

              query GetUser($userId: ID!) {
              user(id: $userId) {
              firstName
              }
              }

              In the query above, userId is a variable. The variable and its type are declared in the operation signature, signified by a $. The type of variable is a non-nullable ID. A variable's type must match the type of any argument it's used for.

              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.

              query

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

              GraphQL server

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

              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.

              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.

              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")
              }
              }
              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.

              mutations

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              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