Building a Location-Based Listings Search Page in React

With our `listings` field in our GraphQL API prepared to accept a `location` argument, we'll begin to work on the client app to build the `/listings/:location?` page for where certain listings can be shown for specific locations.

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

This lesson preview is part of the TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two

Now with our listings field in our GraphQL API prepared to accept a location argument, we'll begin to work on the client to build out the listings page for where we can show listings for a certain location. The listings page isn't going to be very difficult to build, but there's a few things we'll need to think about and handle. When data is available, we'll look to show up to eight listing cards for the listings that are to be shown in a certain page. We already have a component for the listings card, so we won't have to recreate it again. The two child components that will further create in this page, when data is available, are the listings, filters, and listings pagination components. Both of these child components will have very little markup, but will contain functionality that affects the information that is to be shown in the page. When we are to pick a certain filter, we'll be able to have our listings be sorted either from a high to low price or a low to high price, and the pagination element will allow us to move from page to page where every page is to have at most eight listings. We'll attempt to do this step by step. The very first thing we're going to try and do is make the listings query from the listings page and show the first page of listings with the listing card components. Once we make the query and everything is shown, we'll then see how the query can return different results when we provide a different location. At this moment in time, we should be pretty familiar with the pattern of how we construct our GraphQL documents, make the query from components, and handle the different UI of the component under different states of the query result. For example, when the query is loading, or when data is available, or when an error has occurred, etc. So with that being said, we're going to move a little bit more quickly in this beginning portion. We already have the listings GraphQL document constructed that we used for the home page. We'll be modifying this particular document to pick up a location argument for the query, but we'll do that in a second. We won't do that just yet. In the section listings component file, we'll import a few things we'll need to get started. We'll import the use query hook from React Apollo. We'll import the layout and list components from and design. We'll import the listing card component from the lib components folder that we 'll use to show each listing card. We'll import the listings GraphQL query document and the auto-generated Type Script definitions for the data and variables for this listings query. And we'll import the auto-generated listings filter enum from the global types file. We'll destruct and design content components from layout and we'll create a constant to reference the limits of the number of listings we want to show in a page. We'll call this constant page limit and provide a value of 8. In our listings component function, we'll use the use query hook to query for the listings field. We'll pass the appropriate TypeScript interfaces for data and variables and for the values of the variables, we'll specify the filter to be the price low to high value from the listings filter enum. We'll use the page limit constant for the limit variable and for page number we 'll provide a value of 1 for now. And at this moment we'll only destruct the data object from the query result. Note that we're initializing the filter with the price low to high value and the page as 1 since that's the default setting we'll want the user to see when they first land on the page. However, when we build out the filter and pagination capability, we'll need to have these values be kept as part of component state that changes based on user action. But we'll get to that in a little while. Without paying attention to the loading or potential error state of our query, when data is available, we'll look to obtain the listings field from data and we'll use a turnary statement to do this and assign it to a listings constant. Then we'll look to construct a React element within a constant called listings section element. That will be the list we'll build with the help of AntDesign's list component. This list will be very similar to the lists we've created in the home and user pages, where in the list grid we'll specify a gutter of around 8 units. We'll want 4 cards to be shown for the entire width in large viewports, 2 in small and 1 in extra small. The source of data will be the result array within the listings field. And in the render item function, we'll render the listing card components for each list item and pass the iterated listing object down. And in our component return statements, we'll return the content component from AntDesign and within we'll place the listings section element. And now when we take a look at the listings page at the listings route of our app, we'll see 8 different listings be shown to us with our listing cards. Great! Though this works great, our intention for this page is to show listings for a certain location that will be derived from the URL parameter in our routes. However, at this moment, regardless of what URL parameter will add, we'll see the same first page of 8 listings be shown that's located in different areas. To tackle this, the first thing we'll do is in our listings GraphQL document within the lib queries listings file, we'll specify the listings query is able to take an optional location argument of type string. And we'll pass the argument down to the listings field we'll want to query. We'll want to auto-generate the TypeScript definitions regarding this query since we're now saying a new argument or variable can be passed in here. And since we've made some small changes to the GraphQL API schema in the last lesson, in our terminal we'll first run npm run code gen schema to generate the schema JSON available in our clients. And when that's complete, we'll run the code gen generate command to generate our GraphQL related typings. Now with our listings GraphQL query ready to accept a location argument when provided, we'll need to pass it from our listings components. But where are we going to get the value of the location the user is searching for? That will be from the dynamic URL parameter. When a user enters a location in the search bar in the home page or in the search bar in the app header we're going to build shortly, we're simply just going to take them straight to this slash listings page here. But we're going to append a URL parameter. The listings page will take the URL parameter as the location the user wants, make the query and pass that location value along. Very similar to how we've done it in the user page or listing page where we take the ID parameter of the route to find the right information. When we built the functionality in the listing or user page, we've seen how the match object is available in components rendered from React Router. And the match object gives us information about the routes which provides us with further information about the URL parameters. So with that said, we'll do something similar in our listings page. We'll first import route component props from React Router DOM with which we'll use to describe the shape of the match prop in this component. We'll declare an interface called match params to dictate the params in our routes. In the listings page, the param will be looking for is location, which is to be of type string. And we'll declare the match object as a prop of our listings component and we 'll use route component props to describe the shape of this particular props object. Route component props is a generic, so we'll pass the match params interface within, which helps tell our component that the match params field will have a location of type string. With the match object available, let's now provide a location variable to our query and we'll say the value of this particular variable is the location param within the match object. Let's see how our listing page now behaves. When we don't provide a URL parameter, we just see all the listings for all the different locations, with which we're okay with and makes sense. However, if we now try to go to listings slash Toronto in our route, the listings we'll see is only in Toronto. How about if we check for Los Angeles? We'll only get listings in Los Angeles. And if we say United States, we'll get listings from both Los Angeles and San Francisco. Amazing. The main core piece of what we wanted to do in this page is now here, but there 's still a couple of things left for us to take care of. The first small things around the fact that when we're getting listings for a certain location, it'll be helpful to tell the user what location we're looking at. This is why we've added a region field in our listings GraphQL object that is populated with the region that the geocoder has determined based on the location input. So with that said, in our listings GraphQL document, we'll add a region field in our query. Since we've added a new field to query, we'll generate our GraphQL related Type Script definitions again for our client project. So we'll head to the terminal and run npm run code gen generates. And in our listings page components, when data is now available, we'll try and grab the region from the listings field and we'll have it as the value of a listings region constant. When the data isn't there, the listings region constant will be null. We'll then create a constant element called listings region element that when the listings region constant exists will be a title component from and design that simply says results for the listing region. And we'll have to import the typography component from and design and destruct the title sub component from typography. And lastly, we'll place the listings region element right before the listings section element in our listings component return statements. This moment when we provide a location in our routes, we'll see the region that was derived from our geocoder be displayed. If we provide no location and no URL parameter, there will be no region that was found and we won't have it shown as the result for a certain region. Now, if we searched for a region that we don't have any mock listings for like New York, for example, we'll see the region information be returned, but we'll simply see the empty not found state presented to us from and designs list component, which just says no data. Though this is fine, we can try and provide some text to the user to tell the user, hey, no data exists, but if you want to create a listing, go to the host page to create a listing. To facilitate this, what we'll do is in our listings section element, we won't only check for if the listings data is available. We'll also check for the length of the results array from our listings object. If the results array is empty, it means that data has been returned, but there 's no listings. So in our case, we won't show the entire list component and it's not found state. Instead, we'll show our own div element that has a paragraph that says it appears that no listings have yet been created for this region. [silence] And we'll have another paragraph and say something along the lines of be the first person to create a listing in this area. And we'll make the listing in this area portion a link that will take the user to the host page when clicked. So now we'll need to have a few things imported. First, we'll need to destruct the paragraph and text components from the title component. And we'll need to import the link component from React Router. And we'll need to provide the target route path for this particular link and we 'll say when clicked, it'll take us to the slash host route. And now, when we attempt to query for listings or access a route in which we don't have any listing data, we'll see a message is being shown to us that says , "Hey, it appears no listings have yet been created for this particular region. You can be the first person to create a listing in this area." Fantastic. Now, with that being said, there's still a bunch of different things we need to take care of before we finish this section. Notice at this moment in time we don't have a valid loading state or indicator. We simply show that very brief not found state we've created. That's something we need to take care of. We don't have a suitable error state if our query was to ever completely fail. And we'll need to consider and take into account how we want to do the filtering and pagination within this particular page. We'll handle this in the next or upcoming lessons. [BLANK_AUDIO]