How to Build a UserProfile Component With React and GraphQL

With our `user` query now prepared and available in our GraphQL API, we'll begin building the page shown in the `/user/:id` route of our client app.

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 our GraphQL API now able to allow us to query information for a single user, let's focus our efforts to now build the user page component in our client application, which will live in the user ID route. The UI for our user page is going to be a little robust, and it's going to be a few different child components we'll create. We're going to share these images with you in the lesson documentation. Here's how we'll want the UI to look for a user viewing their own profile page. They'll be able to see their user profile, a list of user listings, that is to say a list of listings the user has created, and a list of bookings a user has made as well, both past and in the future. When it comes to how we're going to create these components, the profile section would be the user profile child component to the user page. The listings section will be labeled the user listings component, and the book ings section will be labeled the user bookings components. For viewing another user, we'll only be able to see their profile and available listings, and in their profile we won't be able to see the UI that conforms with how we can connect with Stripe. We'll break this up in pieces. In this first client lesson, we'll look to make the query for the user field, and we'll query all information for the user except for bookings and listings. We'll then create the user profile card and attempt to display the details of the user. If the viewer is looking at their own user page, we'll show an additional section in the profile card, highlighting how they can connect with Stripe, though we won't set up that functionality yet. In addition, we'll create a custom page skeleton component that will be shared for a few different pages and will essentially be the loading page when a query is in flight. So let's begin. We'll first look to construct the user query in our lib graphql folder. We'll create a user folder with an index file. In the index file, we'll set up the user query document. We know the user query needs to accept an ID to query for a certain user. So we'll state ID is an argument to our query, and we'll query for the ID, name, avatar, contact, has wallet and income fields . We'll then re-export the user query constant from the queries index file. To have our schema auto-generate the TypeScript definitions, we'll run the Apollo code gen scripts in our command line. We'll run code gen schema to update the schema file we have in our client. We'll then run code gen generate to generate the TypeScript definitions for our new query. If we now take a look in our user folder, we'll now have the auto-generated TypeScript definitions for the user query. In the user components file, the first thing we'll look to do is run our query when the component first renders. We'll import the use query hook from React Apollo to help us here. We'll also import the user query document and the auto-generated TypeScript definitions for this user query. We'll use the use query hook at the top of our component and we'll say we're interested in having the data, loading and error information from our query results. And we'll want to pass in an ID for the query as a variable. Our query won't return data until we provide an appropriate ID. If we take a look at our client, the ID is available as the query parameter of our URL. React Router provides a pretty intuitive way of passing query parameters as part of props. The route's component automatically provides a prop known as the match prop, which gives details about the route path for a certain URL. If we check for this prop, and looked in our console, we'll see some information about our route. Of interest to us is the params property, which gives us the ID, params and its value. However, TypeScript tells us it doesn't know what the type of this match property is. So React Router gives us a route component props interface that acts as a generic and allows us to help shape the match props for a certain route. Let's see how this can work. We'll import routes component props from React Router DOM, creates an interface that represents our match params, state that the ID param is to be a string, and assign the result of our props as a type, route component props match params. We'll now be able to access the match params ID value in our hook, which would be recognized as a string. At this moment in time, if we take a look at our app, in our network, we'll see the query call being made and the user information being returned. Great. With our query being made, let's look to construct the profile card. We'll have the profile card of the user be created as the user profile component that will be a child to this user component. Let's see how we'll like to render this user profile component before we create it. First, we'll look to get the user object from data if data is available. If this user object exists, we'll have a constant element labeled user profile element that will contain the user profile component and will pass the user object as props along. Finally, in our user component return statement, we'll use a few components from ant design to help style how our layout is to be shown. We'll import the layout row and column components from ant design. Row and column is part of how ant design helps provide a grid UI system. We'll use the content from layout row and column to return the user profile element as part of the return statement. So, we'll use the content from the user profile element. [ Silence ] Our app won't work until we create this user profile component and have it rendered in our UI. The ant design column component gives us props to determine the amount of spacing the children element should take. Extra small refers to extra small viewports and 24 basically states the entire width of the screen. There also exists small viewports, medium viewports and so on. The row component allows us to specify a gutter spacing between columns and whether we want to justify space between and so on. Be sure to check out the ant design documentation if you're interested in seeing more about how these components work. Now, we'll go ahead and create the user profile component. We'll create this component in a components folder within the user folder. [ Silence ] a prop label And in a components index file, we'll re-export the user profile component we 'll hope to create. [ Silence ] to use. [ Silence ] Now, what is the shape of this user prop object? If we take a look at our Graph QL query document, we could look to create the types of the fields the object might contain, but this would be prone to errors if we were ever to change what fields were querying. As we've seen before, we should take advantage of the auto-generated type definitions. The user data interface is being exported and so is the user_user interface, which represents the shape of the user object that we want to access. We could import this user_user interface above, but we'll be more comfortable in only importing the user data interface and simply trying to access the type of the user property within. In TypeScript, we're able to access the type of this property here with something known as lookup types. So let's see this in action. In the user profile component file, we'll import the user data interface from the auto-generated file. [ Silence ] And we'll use the square bracket syntax to denote that the type of the user prop of the component is the type of the user property within user data. [ Silence ] This is very similar to how elements can be accessed but are written as types. This is known as lookup types or otherwise labeled as indexed access types. [ Silence ] With the user prop now available in the components, we can begin to build the component out. The user profile component will be pretty straightforward and will mostly contain just markup to represent the information we want to show. So we won't spend too much time here. [ Silence ] We'll import the avatar card divider and typography components from end design. [ Silence ] We've seen how avatar card and typography works. Divider is a very simple end design component that shows a divider line. [ Silence ] We'll de-struct the paragraph text and title child components from typography. [ Silence ] And in our components return statement, we'll return some simple markup that displays the user's avatar name and contact. [ Silence ] [ Silence ] [ Silence ] [ Silence ] Now we can import the user profile component in the parent user component. [ Silence ] And save the changes we've made in this user component file. When we now take a look at our app, we can see the user profile card being shown. Amazing. This is the user profile card we'll see when we go to any user profile page. Let's now look to add another section below for viewers viewing their user page that addresses the capability to connect with Stripe. [ Silence ] To recognize whether the viewer is looking at their user page, we can compare the ID of the viewer, that is to say the person viewing the app with the ID of the user they're trying to see. Viewer is a state object of the parent index file that is updated when a user signs in with a cookie or signs in via Google. To have this viewer available in the user component, we'll need to pass it down as props. We'll follow the render props pattern like we've seen before and pass the viewer state object along. [ Silence ] In the user components, we'll import the viewer interface from the lib types file. [ Silence ] Declare a props interface that has the viewer prop with type viewer interface. And we'll have the viewer prop be destructured from the props arguments. And we'll use an intersection type to state the type of our props for the component is the props interface and the routes component props interface. With the viewer prop available, we can create a constant called viewer is user that simply checks if the viewer ID is equal to user ID. Viewer is user will be true if the viewer is the user and will be false otherwise. We'll pass the viewer is user value as a prop down to the user profile component. And in user profile, we'll declare the prop and state it is to be of type boolean. And we'll destruct it from the props arguments. We'll now create additional markup in the user profile component that is to be shown only if the viewer is the user. We'll import the button component from and design which should be used as the button to connect with stripe with which we'll set up. We'll create this markup in a constant we'll call additional detail section. We'll have the elements show a divider and some text prompting the viewer to sign in with stripe. We'll also have the button be displayed that won't do anything right now. So if you have a question, then you would like to ask questions? And then if you have a question, then what will you say that you do? So I will change this to a lot of questions, et cetera. That's a hard question, but I think what you can do for the slides, especially because you're doing a really good job of developing your approach. I will get into it in a little while. But thanks a lot, and thanks for giving me a nice,ican- threatening thoughts. [BLANK_AUDIO] To ensure our react element here is to be wrapped within a single element, we'll import and use react fragment, which will help us group the children elements within without adding extra nodes to the DOM. And now we'll have this additional detail section shown below the user profile details in our user profile components return statements. [BLANK_AUDIO] If we now take a look at our client app, we'll see the new additional detail section that tells us, hey, if you're interested in becoming a tiny house host, register with Stripe. We've set this up to only be shown if the viewer is the user. Since we don't have any links to other user profile pages yet, let's grab an ID of a user from our Mongo Atlas collection and try to direct ourself to the user page directly by applying that ID as part of the route peram. And in this instance, we're unable to see the additional detail section, since we're not the viewer. Great. We're querying for a user and we have the user profile card be shown, which displays user information. Before we close this lesson, we'll look to show a loading page of sorts in our query, our user query is in flight, and an error banner if our user query was to ever fail. The page skeleton we'll set up right now is going to be used for most of the route pages in our app. So we'll create this in a page skeleton component within the lib components folder. And in the components index file, we'll re-export what the page skeleton component would be. The page skeleton component will be fairly straightforward. We'll import react fragment and the skeleton components from ant design. And we'll return three instances of the same skeleton component being rendered with each skeleton component from ant design having four rows in a paragraph. Our page skeleton component when used will now show skeleton UI for the page. In our user index file, we'll import the page skeleton component from the lib components folder. We'll also import the error banner component with which we'll use to handle a query error. When the loading of our query is true, we'll render the page skeleton component within the content component. If our query was to ever fail, we'll simply render the page skeleton component, but this time have the error banner component be shown as well. Our error statement will say this user may not exist or even counted an error. Please try again later. And now when our user query is in flight, we'll briefly see the page skeleton UI be shown. Note that the page skeleton UI being shown comes after the loading status for when the user is logging in with their cookie. If we attempted to visit a route with a user ID that does not exist, our user query will be made, but will error and an error banner will now be shown telling the viewer that hey, this user may not exist or an error has occurred. In this case, this user does not exist. Why is an error being thrown in this context? Because that's how we set it up in our server. We throw an error stating a user can't be found if a user document can't be retrieved from the user's collection. We could have returned null instead and handled that on the client, but the outcome will be similar. Amazing. We'll stop here for now. In the next lesson, we'll set up the rest of the user page by having the listing section and booking section of a user be shown. ( bangs )