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 into the frontend
  • Call the useQuery hook, passing a trackId

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 .

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

📄 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 . 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 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 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 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 's useQuery hook to make a call from our client to the .

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 as the hook's first , 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 . 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 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 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 !

The first time we send a to the , 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! 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

  • graphql

  • useApolloClient

  • useQuery

  • gql

  • useState

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.

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

  • second

  • data

  • loading

  • first

  • arguments

  • results

  • isComplete

  • variables

  • error

Key takeaways

  • The useQuery hook takes an optional second , an options object where we can define an object of variables.
  • The stores 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: .

Previous

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.