Build a Grid Column Layout with Ant Design in React

In this lesson, we'll continue to build the listing page in our client application by looking to prepare the `<ListingDetails />` and `<ListingBookings />` components.

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

With the Listing section components now prepared in making our GraphQL query for listing information, we can begin building the UI for this page. We'll first look to build the Listing Details component responsible in surf acing Listing information to the user viewing this page. We'll create this Listing Details component within a folder kept in the Components folder of the listing directory. And in the Components index.ts file, we'll simply export the Listing Details component directly from the Components folder. In the Listing Details components file, we'll import a few things we'll need from the get-go. We'll import the React library, we'll import all the components we'll need from AntDesign, the Avatar, Divider, Icon, Tag and Typography components, and we'll import the auto-generated TypeScript definition of the data returned from the Listing query. We've seen most of these AntDesign components before. Avatar helps display an avatar image, Divider as the name says helps divide sections, Icon gives us the ability to use one of AntDesign's many icons, Typography helps allow us to use the text, title, paragraph elements from AntDesign. We haven't used the Tag component yet. The Tag component is a neat way to display tags to categorize information or just for markup. And AntDesign gives us a lot of different presets to apply colors, make tags removable, etc. We are importing the auto-generated listing data TypeScript definition, since we'll use it to type-check the prop we expect this component to receive. We expect this component to receive the listing object from the data returned from the query, since we'll use it to display the listing information in our UI . As a result, we'll define the props interface for this component, state that this component is to receive a listing prop, and the type of this prop will be the type of the listing object from the Graph QL data returned, and we'll use TypeScript's capability of indexed access types or look-up types to achieve this. We can then begin building the UI for the listing details component. First, we'll destruct the paragraph and title components from typography, since we'll use it shortly, and we'll construct the functional component, state that it expects a listing prop. At the beginning of our components, we'll destruct all the properties we'll need from the listing object prop, the title, description, image, type, address, city, number of guests, and host. We'll now build the template of the component. It will have a div element with a background image style applied to it, which will display the listing image. We'll create a paragraph section to display the listing city. Addres and title. We'll have a section to display the host avatar and name. We'll have a section to display the listing. And finally, we'll give a little more detail by highlighting the listing type. The number of guests the listing can have and the description of the listing. To stress this important point again, the markup and actual styling of how we build our UI isn't the important takeaway here. You're welcome to build the presentation of this UI as you please, and this is the setting and styling we've come up with so far. The key takeaway is how we receive the necessary data from the parent component and how we intend to display it here in this child component. And once again, we'll always be sharing the markup code in the lesson documentation. With that said, let's now see how this component will appear. In the listing section component, we'll import the listing details component from the components folder. We'll also import the column and row components from end design, with which we 'll use to help structure how our child components will appear here. We'll construct an element that is to be the listing details component if the listing is available, otherwise it will be null, and we'll call this constant listing details element. We'll have our main template of the listing component be the content component wrapper. We'll use the row element from end design to constitute that were to display a row of columns and will apply some gutter spacing and say that there should be some space between the columns. We'll construct a column that will take the entire width in extra small view ports and a little more than a half the width from large viewports and upwards, and we'll have the listing details element displayed within. As a refresher, ant designs column structure works with 24 columns, and what we 're saying here is for very small viewports, we want this element to take the entire width that is to say all the columns in the grid, and only for large viewports and up will we want the listing details element to take a little bit more than half. At this moment, if our client webpack server running, we can now look to verify how our listing details component appears. When we head to the browser and we actually navigate to a certain listing ID route with an appropriate ID, we'll see details for this particular listing. We'll get information about the listing image, the city is located in its address, the title, the host information and so on. If we grabbed another appropriate ID for a certain listing from our MongoDB database and navigated to this particular route, we'll see different listing information. Amazing. And if we try to navigate to a certain route in which an ID doesn't really exist, I'll type a few additional numbers here and click Enter, the query would fail because no listing document exists with this ID and we're notified of this in our UI. Great. We'll make a few more changes to our listing details component before we move elsewhere. We'll add a divider between the title and host information. We'll import the shared icon color string we've created before in the libutils file. And we'll use it to style the icon with the appropriate color. Finally, we'll like to have a few items in our details section be links to other pages. First of all, we'll like to have the city label be a link to the listings page will eventually create for this certain city. Similarly, we'll like to have the host information be a link to the user page for this host so viewers can get more information about this host. So with that said, we'll import the link component from React Router DOM. And we'll wrap the city icon and text with a link element that has a target of listings city. Listings will be a dynamic route that will eventually set up where listings for a certain city is shown based on the route that the user accesses. We'll wrap the host section with a link that has a target of user host.id. Host references the person who's created the listing. We have the ID of the host and we can use it here to navigate the viewer to the relevant user page when they click the host information. This is because the user route is also a dynamic route and it takes the ID given to it to get the relevant user. Let's now survey the changes we've made. If you go back to the listing page and look at the listing details, we'll see the icon now have the particular icon color. The divider is now applied between the title and the host section. But more importantly, is the city and the icon section is now a link. If you were to click this link will be navigated to the listings city route where we'll eventually build the page that represents the listings in this city . Similarly, the host section which consists both of the avatar and the host name is a link that takes us to the user page for this host. So for any viewer viewing the page they can survey this host information and see the other listings the host might have created. Great. With the listing details prepared, we'll now set up the small component responsible in displaying a paginated list of bookings that exist within this listing. The booking section to be shown in this page will be very very similar to the bookings or listings section shown in the user page. We've highlighted how the UI was built in the user module. So we won't spend as much time representing the UI to be built in the listing page but instead it looked to replicate what was already done in the user book ings or user listings components. So first of all we'll create a listing bookings folder in the components folder of the listing directory. And in the components index file we'll have the soon to be created listing a bookings component exported from the components folder. We'll copy over the user bookings component to this listing bookings component and we'll make the necessary changes. We'll first import other components we'll need from ant design such as the avatar and divider components. We won't need the shared listing card component since we don't intend for it to be shown within listing booking so we'll remove that. And finally we'll make sure we're importing the listing data type script definition for the listing field query and not for the user query. We'll expect this component to receive mostly all the same props that are shown here. However the main change is that this component should expect the bookings from the listing object, not a user object. So we'll update the first prop here to be listing bookings and we'll say its type is the bookings object within the listing object from our GraphQL data. We'll also remove the paragraph components being destructured from typography since we won't need that in our UI. We'll update the name of the component to be listing bookings and the first prop should be listing bookings. And we'll get the total and result from the listing bookings prop object. And we'll rename any reference to a user booking to now be a listing booking. [silence] We'll remove the paragraph section in the final element we want displayed. We'll add a divider between the top of the parent div element and wrap the rest within a div section. And we'll update the classes here accordingly. We'll remove the listing card within each rendered list item and instead what we'll intend to show is an avatar of the tenant who's made the booking below the booking history dates. We'll get the source of the avatar from the tenant avatar field within each listing booking and we'll give it a size of 64. And we'll add the appropriate class in the list item. We'll update the class for the booking history section. The rest of the actual list element will be practically the same. The only changes we'll make is we'll remove the positioning at the top that we 've done for the user booking section. We'll update the empty text if shown to be "No bookings have been made yet". And finally, we'll update the grid layout for large viewports to only show three elements at a time. Okay, so we've updated this component to now behave as we expect it to for the listing booking section. But let's summarize what this component is expected to do. This component would accept a few props. It would accept the listing booking's information, the page the user is viewing for the booking's list, the limit, the amount, or I guess the limit of bookings to be shown for a certain page, and a function that when run would change the page the user is viewing for the booking's list. The listing booking's component will then have the total amount be retrieved, the result, or that is to say the actual array of bookings from the listing booking's object. And then we use the "ant design list" component to construct the UI for this paginated list. In the data source we pass in the result which is the list of bookings, and in the pagination prop here we construct how pagination is to behave in the UI. We dictate what the current page should be, what the total amount is, the default page size, and so forth and so on. And the "on change" handler is what "ant design" retrieves, and we use it to then call the set bookings page function that will pass in. The render item prop here within this listing list component behaves as the render props pattern and react, and allows us to render what we want for every list item. We want to show the booking history for every booking that's been made, as well as an avatar for the tenant that's made the booking. And finally, we simply check if this list exists, this list will only exist if the actual array exists, and if it doesn't, we simply have the entire component display "no". Okay, let's now have the parent listing component render this child component when bookings is available. In the listing component we'll create the listing booking's element constant, if listing bookings is available, we'll pass in the listing booking's prop, the page prop, the limit prop, and the set bookings page function prop. And we'll look to render listing booking's element right below listing a details element. And we'll need to import the listing booking's component from the components folder. And our listing component should now be rendering the listing booking's element or listing booking's component when bookings exist within the listing object. Okay, so let's see how our UI can now behave. When we look at the listing of any other user, at this moment in time we don't see any booking's information. If we recall, in the server, we've only authorized the booking's field within listing to return information if the viewer is viewing their own listing page, which is why if you look at the network logs for this particular listing as a signed out user, we'll see that booking's is null. And for the most part, bookings will always be null in this case. So for us to actually see the booking section, we have to be signed in. Now as a signed in user, if I try to go back to that same listing for another user, I still can't see the booking's information. Okay, at this moment, my user account doesn't have any listings associated with it. And if it were to, it wouldn't have any bookings associated with those listings . But we still would like to mimic or at least observe how the booking section in the listing pages to behave. And we can try to mock this behavior just for this use case for now. One thing we can do is we can go to the Mongo Atlas dashboard, we can update the database and the collections to basically say that my user account has a listing and that listing now has a series of bookings. That would be probably the more robust way of doing so, but we'll go with a simpler approach just for this example. And what we want to assert is we want to simply assert that the listing booking 's components displays the correct UI if the bookings field within the listing object from GraphQL exists and is populated. So to do that, what we can do is we can just simply take a mock data object we can prepare. So here we have a simple mock object for how the bookings field would behave for a listing object if there were four bookings. And if the result for each of these bookings were from the same tenant with different check-in and check-out dates. We'll provide this mock array in the lesson documentation. But we can just simply take this mock array and we can say this listing booking 's constant we have here is going to be equal to this mock array. And to help avoid any TypeScript warnings or issues just for now, we'll say as any and we'll cast it to the any type. Now if we save this particular file and we head back to the UI and scroll to the bottom, we now see the bookings UI that we expect to see. Each of these elements or items within the list shows the check-in and check- out dates as well as an avatar of the actual tenant. And it's a paginated list. If we click the next page, our work would be done for us and the UI would reflect the updated page and show us the information for that page. Great. So the last thing we'll do before we close is as a viewer, if we want to see more information about this tenant, we'd like to go to their user page. So at this moment, all we see is an avatar. So it'll be nice if we can have these elements or these avatars be links to the user page. So what we'll do is in the listing bookings component, we'll import the link components from React Router DOM. And in the avatar, we'll wrap the avatar within each list item with the link component and the target path for this link component would be the user page and we'll apply a dynamic parameter of listing booking.tenant.id. The id field exists with the tenant object that actually exists within each booking in the list of array. So now that was to save this file and about head back to the UI, each of these avatars at this moment in time will now be links to their respective user page. Great. That will be it for this particular lesson. Before we close, we'll go back to the listing component and we'll make sure we're not using the mock data array. We'll go back to actually trying to access the bookings field within the listing object and we'll make sure to save our changes. And when we eventually get to the point that bookings can be made in our app, we'll come back here and we'll try to verify that this functionality works as intended. One quick note to make before we close, we're currently looking at the server project here. When we built out the user page in the previous module, we effectively wrote some explicit resolver functions for a few fields within the booking graph curl object and the listing graph curl object. That was necessary because we needed to query for certain fields from these objects in the user page. And by building the listing page, we've practically built most of the resolver functions we needed. However, there's one certain field within the booking graph curl object we haven't set up yet. The tenant field within a booking document in the database is a reference to the ID of the user or the tenant who's made the booking. However, in our graph curl type definitions, we've explicitly stated that we want the tenant to be the user object. We haven't built a resolver function for the tenant field yet. And in our client project, what we simply did was use a mock data object to basically specify that the UI for our booking list in the listing page works as intended. However, if we were actually to have bookings in the database at this moment, we'd have to build this resolver function. And it's practically the same as what we have here. Essentially, the only difference is we're simply going to look for a user document in the user's collection where the underscore ID fields equal to the booking dot tenant field. However, once we get to the point in our course where we create the bookings and actually create the functionality to have a booking be made, we're going to create the tenant resolver function at that moment in time before we verify everything works as intended. So this is just a very quick note to specify that we haven't specified or created the tenant resolver function that we need to create. The check in and check out fields for a booking object is to be trivially resolved. We have explicit resolver functions for the ID and listing fields, but we haven 't set it up for a tenant just yet. And we're going to do it at a later point in the course. [BLANK_AUDIO]