Chakra UI Component Library, Next.js, & GraphQL With Apollo Client Part 1
Building a GRANDstack Podcast App: Episode 5
In Episode 5 we focus on using the Chakra UI component library for React as we continue to build out our podcast application. We also cover using Apollo Client for GraphQL queries and mutations and how to implement optimistic UI with Apollo Client.
Links And Resources#
- GRANDcast.FM code on GitHub
- Chakra UI Docs
- Blog post from Jim Raptis on building a landing page with Chakra UI
- HTML audio element guide
- Guide to working with flexbox CSS
Hi, folks. Welcome to the NEO4j stream. My name is Will and today we are continuing on building out our podcast application with GRANDstack that is GraphQL, React, Apollo, NEO4j database. Today, we are going to be focusing on adding some nice looking React components. So some styling UI library to our React application and we're going to be using the Chakra UI Library. Let me drop a link to this in the chat. Here's the docs for Chakra UI. So there are a lot of options for design systems. I've been using Chakra for a few applications recently and I really quite like it. I think it's a good abstraction and a good sort of intermediate area in terms of some other libraries. And we'll talk about how Chakra compares to some other component libraries you might be familiar with. If we look at the, actually, let's just jump to the comparison section and talk about how Chakra compares to other component libraries and design systems that you may have seen. So I think Tailwind is very popular. It's sort of a CSS utility class library, I guess. Maybe sort of comparable to Bootstrap. Tailwind is really nice. I know a lot of folks that use it. For me though, with Tailwind I think you really need to have a sense of what you're doing when it comes to sort of the design in the CSS that you want to accomplish. Whereas in my case, mostly what I'm interested in are sort of nicely styled, prebuilt components that I can take advantage of in my application. I think also Chakra could be compared to Material-UI. So Material-UI has a great React component library that we've used in the past. If you've used the GRANDstack Starter project that actually bundles Material-UI by default, I would say that Chakra UI is a bit lighter weight than Material-UI. Material-UI has a certain sort of look and feel and a large library of components that you can do things like a data table. Whereas Chakra, I think, has a bit more the base components that you can then sort of compose together to build what you are looking for. So, one thing that's really nice, we can see here in the docs all the different components that Chakra UI includes. But we can use any of these style props on any of these components. So that's one really nice feature. So in this example, let me zoom in a little bit here. In this example, we have a Box component and if we want to add two pixels of margin, we just say M equals two. That's the M is margin. Sort of short property or prop. Cool, so let's fire up our GRANDcast.FM application, just so that if you missed the previous episodes, I'll drop a link to the code in the chat here. And the GitHub README for this project also includes links to all the previous live streams. So we are currently on Episode Five. So last time we were focusing on getting started with Next.js and setting up client-side GraphQL authentication for our GraphQL API, so that we could see our subscribed podcast and our episode feed on the front end. There's also blog posts write-ups for most of these that are linked as well. So if you wanna check out the sort of written form, I write these up after we do the live streams. Cool, so let's make sure we have everything running here, so NEO4j. So we've got NEO4j running in NEO4j desktop. If we take a look at NEO4j browser, at the data model, just to refresh our understanding of the data model. So we have playlists. We have episodes that are in a playlist. We have a user who subscribes to podcasts. Podcasts have an episode and then a user owns a playlist. So that's the basic data model that we have. If we take a look at, maybe let's take a look at some episodes here. So this is an episode. It has some properties such as the audio file. The date it published, show notes and so on and I can traverse the graph to see this is an episode of the connected podcast. I can see other episodes. I can see the user here who subscribes to the connected podcast. We're storing a hash of the user's password. Remember this time for this application, we chose to kind of roll our own Auth Service instead of use something like Auth0. So we are taking password input from a user, we're hashing that, comparing it with the hashed password in the database and then our GraphQL API is generating a JSON Web Token which we're using for authentication. Which we will see what that looks like at the API in a second. Cool, and we can see this user has some playlists and subscribes to some other podcasts. Cool, so that's the database. Let's go over to our code here and we'll start the API application. This will start our GraphQL API, so it says that's running on localhost 4001/graphql and here is GraphQL Playground. Looks like right where we left off. Yeah, we even have a authorization token in the header. So like I said, we're using JSON Web Tokens for authentication in our GraphQL API. We saw last time there's a, if we look in the docs here for our GraphQL schema, there's a signup and login mutation. So the login mutation, we give a username and password, our server generates the JSON Web Token and sends that back and then we attach that token to the next request headers to make authenticated requests against our GraphQL API. So in this case, let's go ahead and run this query. This is the episode feed entry point. Just give me a minute, make sure that's big enough. The episode feed entry point, so we're saying for this currently authenticated user find their 50 most recent episodes and return ID title, the audio file and then the name of the podcast. Of course, that's the API. Let's go over here and in the next-app directory, last time we started a Next.js application. So Next.js is a React framework that has a lot of batteries included functionality like routing. It has image optimization. It has something that we haven't taken advantage of yet, but it actually has the ability to add API endpoints in the Next application. So currently we have a separate GraphQL API application, but I think in a future episode we'll see how we can migrate that into our Next application. So let's start our Next.js application. It says now that's ready at localhost 3000. Okay, and I have a signup form. So let's try to sign in here with one of the users we created last time. Cool, so this is the episode feed. So when I'm signed in, I get a list of my 50 most recent podcast episodes. I can sign out. I can sign in as a different user. So here's another user that we created and yep cool, I get different episodes. Okay, cool. So that's where we left off last time. This isn't very useful, though. Like I can see the episodes. I can see my episode feed, but I can't see any details about these podcast episodes. I can't actually play them and listen to them, which is sort of the main points of a podcast application. So what we're gonna do today is build out some of that functionality. We'll start, I think, with the episode feed. So turning this sort of bullet point list into, I think we'll build out episode components that show us like the show artwork, the summaries, the show notes of an episode, allow us to listen to the episode and then we'll also add other views for our playlists. So we have a playlist GraphQL endpoint or GraphQL entry point. So for every playlist I can get the name of the playlist and the episodes. So again, this makes use of the authorization token that I've passed in to find that user in the database, to find any playlist that user owns and then any episodes attached to that playlist. So we wanna be able to work with playlists in the front end. I wanna be able to view my playlist, create a new one, look at the episodes in there, play the episodes. And then also, we wanna implement the front end functionality for the podcast search. So we have a podcast search GraphQL query field. I dunno, search for hiking podcasts. And the podcast search, this uses the podcast index API. So we're actually wrapping a REST API with this part of our GraphQL API. But anyway, so that's the functionality that exists in the API, but does not exist yet in the front end of our application. So that's what we're going to build today. And we're going to use Chakra UI components to make it look nice. So let's get started first by installing Chakra. So I'm going to stop my Next application and install the Chakra packages here. While we're at it, there's also, not an alert, but there's also... Where are the icons? The icons? There is a Chakra UI icon library that I also want to make use of. So let's install that as well, chakra-ui/icons. And this icon library has a whole bunch of SVG icons that we can import as React components. Cool, so the first thing I wanna do, I think is let's add a responsive header at the top here. So because we're gonna have these other views, these other pages in our Next.js application for this one'll be the episode feed, but we also want to have a playlist, podcast search, maybe view of the podcasts I'm subscribed to. So we'll wanna be able to have links up here and we'll want the header to be responsive in case we're on a mobile device. So, Chakra UI doesn't have a header component by default. So I'll have to compose a few components to build this out. There is in the resources, there's a couple of examples of how to build these sort of responsive components. One that I thought was quite nice is this blog post about building a landing page. So this includes how to build the responsive header like this, but also how to build hero components and things like this for a landing page. So I'll link this in the chat. So we're going to do something very similar to what is described in this post for building our responsive header. So we've got a question in the chat. Can I get NEO4j Nodes and Relationships Graphical view in Angular? Yeah, it's a good question. I unfortunately know very little, maybe less than little, know nothing about Angular. So I can't really help you there. I'm sure there are some resources. If we take a look, I think there was a visualization example. Was this on the NEO4j blog, I wanna say. There's a visualization series on the NEO4j Medium publication. Here we go. So this visualization series, there's a bunch of articles here with different examples and I'm pretty sure there is a Angular example. Yeah, we can do it in React to say. Yeah, so I would say this is a good resource to see just sort of the various, different node chart visualizations is a lot of what this is focused on. That should be helpful. So I would start there. Cool, so we have now installed Chakra. We know we want to create a header, so let's dive into it. So the first thing I'm going to do is in our next-app, let's create a new directory called components. And we're gonna create a new component, new file rather, called Header.js. So a question in the chat. I'm struggling with folder structure and file naming sometimes capitalize, sometimes with a dash, sometimes with an underscore. Does it really matter? Yeah, that's a good question. So I think it's good to have some sort of consistency there in terms of like a style guide for naming files and variables and this sort of thing. So I think... There's an Airbnb React style guide. Yeah, so Airbnb has a pretty extensive React specific style guide that they publish. They also have I think, one of the most popular ESLint configurations. So, you know I think that and they have sort of opinionated ways for naming classes and files and folder structure as well. So I would say just pick something that makes sense for your workflow and that's consistent. A lot of folks like to just sort of adopt one of these style guides, if they're not working on a project that has an opinionated style guide already. And if you adopt that style guide, then there aren't really any discussions or any debates about the best way to to name things. You just follow the style guide and that's it. And you know things like Prettier and ESLint, which we have, I think configured by default with Next.js can help with code styling. It doesn't address the file naming and those sort of issues, but it does help with how to format and style our code so that we don't have to think about it. So we have Format on Save set up here, so anytime that we save our file, it gets formatted. Anyway, let's create our header component And our header component, let's jump over to the docs here. The sort of outer component is going to be a Flex. So let's import Flex from chakra-ui/react, I think. And we are going to return a outer Flex component. So what this Flex component? So Flex, like the docs say, is like Box, but with display: flex. Okay, so what is Box? So Box is basically, just a wrapper for a div, but we can then add some of these styling props to it to sort of add some styling for the component. And so Flex is like Box, but with display: flex. What is that? Well, display: flex is the CSS for Flexbox which there's a really helpful, this, yeah, this one, I think. This is a really helpful guide to explaining what is Flexbox and how we use it. And basically, it's a very, very helpful way to build responsive layouts in CSS. So we're going to take advantage of this to give us the responsive aspect for our header. So, this will be a Flex component. Let's add some style props here. Let's add some mb. So what is mb? So mb is one of these style props, that's short for margin bottom. So we're gonna give this eight pixels of margin at the bottom. Let's also do the same for padding. We want this to render instead as a nav element and we want things to be center aligned. And we can justify the text here using space-between. And let's set width to be 100% of the container. Okay, so this is now the Flex which will represent this piece up here for our nav. It'll be 100% of the width and 'cause we're taking advantage of Flex, we know we'll have a responsive layout for the items that we put in there. That's great. First, we're gonna have sort of the logo or the name of our site over in the left. So let's create a Box with a fixed width and in that Box, let's have just in text our sort of logo, if you will, for our application. Call that GRANDcast.FM. and let's make sure we are importing the Text component and the Box component. And then let's export default Header. So we're exporting our header component. Cool, let's figure out now how to add this header to every page. Well, in Next.js in pages, _app.js is sort of our main level app components. And so what we can do here is import our Header from /components/header. We skipped a setup step that I think is documented here. Yeah, where we need to wrap our top-level components in the ChakraProvider. So the provider pattern in React allows us to sort of save things in what's called the Context API. That's sort of global for all of our components. So it basically allows us to inject things into the React component hierarchy. And in this case, Chakra has a provider that, one, can keep track of themes. So we can create a theme. Chakra has a default theme which I think is what we're gonna use today. But if we have some like brand colors or specific styling that we want to be implemented across all of the Chakra components that we're using, we can pass that in with the ChakraProvider. It also exposes these React Hooks through the ChakraProvider as well. So let's add that, also, in our _app.js file. ChakraProvider from, I turned off my IntelliSense, it's a little more readable on the livestream, but missing that. Okay, so we have our AuthProvider from our last time we built this auth service. So we have the AuthProvider here I guess our ChakraProvider doesn't really matter. It can go outside or inside the AuthProvider. I don't think it matters. Cool, there's our ChakraProvider and then we want to add our Header component here sort of before our page component. Okay, so let's start up our application and let's see if we have our header. Nope, we have an error. Cannot find header. Oh, it's not a, it's a default export, not a named export. Cool, there we go. So here's our header. There's not much there yet. We have the logo. We also notice that because we have now imported Chakra and the ChakraProvider that now our form is styled a little bit. Cool, so in the chat. So regarding like naming again, take the underscore all the time. That's fine. I won't look stupid. Yeah, I mean, you know projects and languages have different conventions. So, like I said, I would find sort of a style guide that describes what are best practices for the language or the framework that you're using and try to adopt that. But I think as long as you're consistent, like as long as you're not sort of mixing the different way that you're casing things, mixing the different ways that you're using dashes versus underscores, you know I would say just pick something that makes sense for you and that's fine. A question from Daniel, Fullstack GraphQL applications with GRANDstack would this be updated for the newer NEO4j GraphQL once it's out or released using NEO4j-graphQL.js. Yeah, that's a good question. So, so far in our application we've been using the new NEO4j-graphQL.js library. There is a new library called, it's namespaced under NEO4j/GraphQL. And this library will eventually replace NEO4j-GraphQL.js. This is, it's sort of the official, product-engineering version of the GraphQL integration. So this is currently out in Alpha release. I'll drop a link to this in the chat. And so the question is, specifically, is the book gonna be updated to use this library? Yeah, so I think we will. I think we will. This library is currently Alpha. So it's kind of too early, until this library is it but more stable. So we'll see when this library becomes a bit more stable and has sort of a final release. Then, yeah, I think we will update the code samples and the examples application for sure. And maybe we even do that for this podcast application, as well, to see sort of what is involved in converting from one to the other. Cool, so let's get back to our header. We so far have just done the logo. We don't actually have any items. Daniel says, thank you, looking forward to the updates. Cool, great. Glad you're enjoying that. Yeah, there were I think, two chapters released last week, earlier this week maybe? So for those of you who don't know, this is a book that I've been writing that is currently available in early release. There's now seven chapters out. So two chapters that were released last week or a few days ago. It was Chapter Six which talks about Client-side GraphQL. So let me zoom in a bit, so Client-side GraphQL. So similar to like what we did last time on this live stream, how do I set up Apollo Client? How do I use the React integration for Apollo Client? How do I keep track of client states with Apollo Client? That was one interesting thing that we cover here. And also, some ways for dealing with Optimistic UI with like local only fields and reactive variables which is a new feature in Apollo Client 3.0 that I think it was really interesting. And then Chapter Seven, which digs into authorization and authentication as well. So yeah, it's still a work in progress. I have a couple more chapters to go. There is a free version of three chapters. So grandstack.io/ebook will take you to a download for a three chapter excerpt focused on the backend pieces. So I think that's Chapters One which is sort of the overview, Three which is an overview of NEO4j and then Four which is a look at how to use GraphQL with NEO4j from the backend perspective. So if you're interested in the book, try that free download and read through those three chapters for free to see if it's something you're interested in. But cool, yeah, thanks for the question. Another question, what should be done to ensure the user stays logged in during page fresh, new window, et cetera. Yeah, so currently when I sign in, if I remember the password. So currently when I sign in, if we take a look at our auth service, which is in lib, auth. So what happens here when I sign in, we call the login GraphQL mutation that's gonna compare the username and password and it'll hash the password and compare it, right. We're storing the hashed password in the database. And then if that password is correct, then we get back a authorization token. And we're just setting that AuthToken as, where's the set? SetAuthToken is just a state variable which we are persisting in this provider sort of, again, through the Context API. So we're not sort of storing that token, as say, like a cookie. We're just currently keeping that in a state variable. So, you're right. So, currently if we refresh the application or if we open a new window, then we lose that state. So we'll need to figure out some way to persist that. Cool, great questions. Okay, so on our header here, we just have the logo. We need to start adding some items to the logo. So let's put this in another Box component and here's sort of where the trick happens for the responsiveness. So in these like hamburger menus, I guess is a good term to call them. Like, I have one on my website. So here, when we're in sort of the full width on a desktop, we just have all of the different links up at the top. Maybe we have a button or something. And then when we go to say like medium width switches to this hamburger dropdown and we get this sort of responsive layout. So that's what we wanna do here and the trick sort of for how we do this is that we can set the display style prop in sort of base mode. So when we're in sort of the regular full width, but then once we hit medium, so medium and above, then we're going to hide this element. And this element, this is gonna be our toggle for the hamburger menu. So we'll need to define that toggle menu function in a second here. And then we also need to keep track of whether or not we should be showing the CloseIcon or the HamburgerIcon. So we'll need to define a state variable to keep track of that. So let's do that. So, first let's import useState from react. Yes, toggle menu is not defined. A lot of things are not defined. That's okay. So let's create a new state variable. So show is the state variable and setShow is the function that we use to update it. And useState takes the default initial value for the state variable. So this is whether or not we should be showing like all of the items in our menu and we need to implement this toggle menu function. And this is going to just call setShow to toggle the show state and missing an equal sign there. And let's bring in our icons. So we're using a CloseIcon and HamburgerIcon from chakra-ui/icons. So, cool. There's our hamburger. So we don't see the hamburger when we're full width. When we start to shrink down than we do and we can toggle it between Hamburger and Close. Cool, so that's good. That's what we want there. Now, we need to actually add some items, some menu items. So I think let's have another Box. And again, we're gonna have different value for the display style prop that is gonna depend on, okay, is show toggled? If it's not, then we're gonna be hidden. And then for once we hit size medium, we wanna show that in flexBasis, which I'll talk about it in a second, 100% in the base mode. And then for medium, set that to auto. And so flexBasis, if we take a look, I think this is covered. Yes, so flexBasis defines the default size for an element before the remaining space is distributed. So we wanna do is take up 100% when we have our elements. It's probably easier to have the elements to show. When you have our elements, we wanna take up 100%. Okay. So we've got a Box and now we're gonna have another Flex for our, so the Box represents sort of all of our menu items. And then within that, let's have another Flex. And the reason we're adding another Flex inside the Box, I guess, is the case to handle things like buttons where we want our menu items to be grouped. But then we want to flex with a button that has maybe a different position. So this gives us some flexibility to do that. And we're gonna specify, similar to how we want the justify text layout to work we'll need to do this above. So for different breakpoints, we'll use different text justify. Flex-end, this just means that the items will be oriented near the end of the Flex container. And the direction, this is really important one, direction in Flexbox and again, the style prop takes an array 'cause we get a different value at the different breakpoints for how wide our browser is. But direction, let's go back to our example for Flexbox direction. Yeah, here we go, flex-direction. So flex-direction can either be columns or rows. I think the default is row. And so if it's row, then we're gonna have things laid out like this, horizontally. If we have direction column, we'll have things laid out vertically. And so this is the trick that we use to have sort of those items drop down in a more responsive manner when we go to smaller width. So when we're in the small breakpoint, we will have column direction flex and row, otherwise. Cool, and let's set some padding, as well, at the top for our different breakpoints. And now we're gonna have our actual menu items. So this component doesn't exist yet. We will write this one next. So our first item will just be home. We'll link that just, is that text big enough? Is that too big, maybe? Yeah, it looks good. Menu item, let's have another one, maybe /podcasts. And another item, that will be Playlists, maybe? And Search. Okay, so we need to implement this menu item component. So let's do that up here. It's just gonna be specific just to this header component. And so the menu item, we'll take the children so we can put in whatever sort of if we wanna have a button or a text, whatever will work. We'll also have this isLast prop which we'll use to adjust to the padding and the margin for the last thing in our list. And we'll set a default value for where we're going to link to. And this is just going to be a text with some margin at the bottom that is going to depend if we are the last, we won't add any margin at the bottom of the menu item for the last one, but otherwise, we wanna have some space between our menu items. And we need some margin at the right. And in this case for the, if we're the last we need to add some margin there. And we'll set display block and we're going to link to the to and then include any child elements. Okay, what is this link, though? Well, Next.js has a router built in. So with this link component, so we'll pull that in from next/Link. And I think we should be good to go there. So here we have all of the different menu items. When we shrink that down, something's not quite right. What happened there? Let's see. So our top Flex and our Box, unclick. The Flex center direction hm-hm-hm, has a nav width. We want some wrapping here. There we go. So we're missing the wrap style prop. Cool, so you can see how the different breakpoints apply using our different Flex styles here. Cool, so that is our hamburger menu. The next thing we want to do is dive into the episode feed. So when we're signed in, oops. That's another thing we need to handle is error messages a bit better when a user signs in, or fails to sign in rather. Cool, Tomas says, cool hoodie. Thanks. I got it for giving a talk at the NODES Conference. It was a speaker swag for the NODES conference which reminds me, that's a good thing to bring up. So NODES is the NEO4j Online Developer Expo and Summit. It's kind of the online conference for NEO4j and it is coming up again in June. I'll link this in the chat, but what is coming up much sooner than June is the Call for Papers. So if you'd be interested in giving a talk at NODES in June, I think the deadline is April 1st maybe, something like that? April 5th for submitting a talk proposal. So I'm not sure if the speaker swag will, again, will be cool hoodies like this, but anyway that's where this one came from was from giving a talk at NODES last time. Cool, so here's our episode feed. If we jump to pages, index.js, that's where this lives. I'm just scrolling through our errors there. Yep, that's fine. So what we're doing currently, we've defined a SignIn component that has this form. And we have an EpisodeFeed component that has a GraphQL query to fetch the user's episodes and then maps over each episode that comes back from the GraphQL API and just returns the list item with the ID and title. Then there's a Sign Out button. And then here's the component that is checking to see if we're signed in using our Auth Service. And then if we are not signed in, we render the SignIn component. Otherwise, we render the EpisodeFeed component. So let's pull this SignIn component out, 'cause I think we'll probably wanna be able to reuse this in a few different places. So let's go to components, new file, SignIn and let's check the Chakra components for forms. So we've got a lot of options for forms. FormControl, this is a good one that allows us to include things like error message, some client-side validation. So this is a good sort of maybe wrapper component to use for our form. So this needs to be its own component. So let's import a few things. We're using useState from React. We're using the Auth Service and we're going to use some things from this form example in Chakra. So FormLabel, FormControl, Button and Input, I think, is what we need in Chakra UI React. Cool, and let's change some of these. So instead of form, we'll say formControl and we don't need this onSubmit. Let's add b. So b is for border. So it'll have a one pixel border around our form. We'll give it an ID. So just sort of using one of these examples. I guess this example, simple enough. Let's change this to formControl instead of form, then we can add a formLabel. Add some margin on that, so it looks nice. FormLabel Sign In. Then we'll change this to the Input component, not just an input element, but we wanna specifically use the Chakra component. So our input type, text, placeholder. Let's add margin style prop here. And the onChange, so when we update the value in this form field that updates the Username state variable. Yep, that looks good. Let's add some margin here, too. And our button, we'll change this to use a Chakra Button. Let's set the width to be 100%. If we look how that is going to look, I think that'll be a bit nicer. And let's add an onClick handler that will call onSubmit. And onSubmit calls our SignIn service, which is good. So let's save that and then here back in the index page, we're gonna import SignIn from components. SignIn. Oh, did I forget to export? Probably. Yes. Let's export that. Export default SignIn. Okay, let's see if that worked. So let's do a hard reset. Okay, here's our form. Why's our button kind of funky? Oh, let's add margin on the button as well. Yeah, that's okay. Make sure our form works. Okay, that's the SignIn component. So the next thing we wanna do... Well, let's see here. I guess what we want is really an Episode component. So that here as we iterate over this list of, as we map over this list of episodes that come back from the GraphQL query, let's take a look at what that looks like. Not podcastSearch, but episodefeed. First 10 episodes; title, audio file, podcast, title, things like this are gonna come back. As we iterate over each one of these, let's create an Episode component and we'll just pass each episode object as props to that component. So back to our components, let's create a new one called Episode And let's see, Episode as props, I guess it'll take an episode object. We may pass some other things in there as well. And what do we want to do here? Well, let's initially, let's just return a div with like episode title, episode audio, actually, what are all of the things that we want? I don't think this query, actually, covers everything that we need to render. So we think of what we want our Episode component to look like, I'm thinking of like a rectangle with our image, our sort of show art for the podcast. That's typically always a square image. So a fixed size on the left. At the top, maybe like the title. We have show notes for each podcast. That's the summary property. And I think what we wanna do there is, instead of just showing like a wall of text, 'cause we don't know like how long the summary show notes are going to be, so Chakra has a Accordion component that we can use. So I think what I'll do is like at the top of our Episode component, we'll have the title. And then when you click, we'll show the show notes, so you can like hide those and then we'll wanna have like the audio elements so we can play the audio file in the browser. So anyway, we don't have everything that we need here in this GraphQL query. So let's update that. Let's figure out exactly what we need here. So we need ID, title, audio, the title of the podcast. We want the summary, that's the show notes. We want the pubDate which we can either grab things like day, month, year or we can grab the formatted. Let's just grab the formatted version of that. Actually no, let's grab the, let's not worry about styling that or formatting that date client-side. Let's scrub day, month, year and we'll put those together. So formatted is, oh, we can look at this. Formatted is the... See here's an example of some really long show notes. Here we go, formatted is this representation, which to show in our Episode component on the front end, we'd have to parse and format that. But we can also just do day, month, year. And then we get back an object with the day of the month and the year. Okay, anything else that we need for our Episode component or is that good enough? We need the image. Um, I think we want the image on the podcast, actually, since I don't think every podcast episode has an image. This one does. Oh no, that's the podcast. Does the episode have an image? The episode does have an image. Yeah, okay. We'll figure out which one of those to use. What else? PubDate, Title, link. Link would be good. Yeah, we can link out to the website of the podcast, but cool, I think that's good. So let's replace the selection set in our episodeFeed query. Oops, etra bracket. There we go. Okay, so now we have a bunch more fields. So let's just play all of these. Not play all these, let's just show all of these. I've got podcasts terminology on the brain. So what, like ID. Conrad says how about adding the duration? That's a good one. I don't know if we actually grabbed that in our feed parser. Let's look. Let's look at our types here. So episode has a pubDate, summary, title, link, image, audio, which is the audio file. But yeah, we didn't, actually, grab the duration in our feed parser. So that's something we'd have to go back there and grab. The HTML5 audio component, I think will show us the duration from the audio file, we'll have to see when we get there. So I think we'll have that, but that would be nice to be able to like sort by that or something like that. Episode.podcast,image, so something like that, just to make sure we have the data export default Episode. Cool, so now let's import Episode from components and then here instead of returning a list item, we will return the Episode. And what we want to do here for the props, I think we just need to pass a key. So some unique ID so that React knows when to rerender this component. That's kind of needed anytime that we're mapping over something and returning like an array of components. And then for the other prop we said Episode and that's just this value V from our map function. Let's not wrap this in a unordered list. We don't need that. Something like this. So this is gonna be sort of vertically stacked and Chakra has a VStack component, I think. A Stack component that's good for lists. So there's a VStack flavor of this. So let's return a VStack with some spacing between each one. And then let's set width of that to 100%. And Vstack, import that from chakra-ui/react. Okay, so here's the early version of our Episode component. We have it in the list. We have some spacing between them. That's fine. Okay, so now we want our Episode component to be a bit more impressive here. So we're gonna take advantage, again, of the power of the Flex component and Flexbox. So let's import that from chakra-ui/react. Okay, and it looks a little wonky. You can see how it tries to do that sort of responsive layout and you can see how the default is the row direction. You can see how the different elements are laid out horizontally Okay, so let's add a border, which I think, is just b for the shortcut style prop in Chakra. And we can set rounded to give sort of a rounded look around these. Is b not, where's my border? There we go. So it's not b, it's actually border. Okay, there's our sort of rounded, rectangled border. So over on sort of the left part of our Episode component, we want to have the show art. Let's put that in a Box, because we might want to add other things underneath the image. Like maybe we'll have like a action items button and be below the image like if they wanna add it to a playlist or something like that. So for this one, let's set a fixed width. Let's go with 150. And we can get away with the fixed width here because our image that we're gonna bring in, so there's a Chakra UI has an Image component. Which is quite nice that I can set different sizes and styling for the image. But we can get away with the fixed width here because all of our images are gonna be the same dimensions because they are podcast show art, which by default I think is like 300 or, yeah, 300 by 300, something like that. So our image box size is gonna be 150. The image source is gonna be the podcast show art and let's set some margin here. Box, so we need Box. We need Image. I think those are the only Chakra components we're using. Oops. We need to persist our auth token, definitely. So we've got too much here. We're just sort of overflowing here. Let's comment out some of the stuff that we don't really care about. Cool, here's our image. So we've got our show art. Now the next thing we wanna do is we said we wanted to have the title. So we've got like the title at the top here. And then we wanna use that Accordion component, so that when you click on the title, we then drop this accordion down and show the show notes. So, when our Episode component is a Flexbox in direction row, so the box that we have that's currently wrapping our image, that's gonna be on the left. It's in its own Box. Because later on, like here, we're going to put a button for actions we wanna take on the episode and then to the right of the Box, let's add another FlexBox. And this one is gonna be direction column, because we want things to be laid out vertically. So we're gonna have like our title with the accordion dropdown at the top and then maybe like the date. And then below that we're going to have the HTML5 audio element so that we can play, pause, stop the audio file. So let's add some margin on the left here, 'cause this is gonna be to the right of our image. So we wanna scooch it over a little bit. And then let's make sure we take up 100% of the remaining width within the larger FlexBox. And we might wanna set, because we want to just sort of render these in a list, let's set a max width. So we've been using the style props, but you can also use like the more common style prop to do sort of the standard CSS in JS. So we can say max width is 700 and set width to 100% for our outer FlexBox here. But anyway, okay, cool. So in this FlexBox, we want our accordion. Well, we want sort of the title at the top with our accordion. So let's have a div where we're gonna kind of wrap that. And if we look at the accordion example, let's look at this example. So Accordion, gonna say allowToggle. These accordions are set up so you can have multiple of these. These are Accordion item components. We just have one. So we have an AccordionItem and then this let's put this in a header and that is gonna act as our AccordionButton. So our title, the whole thing is gonna be the AccordionButton. And then let's put this in a Box and align text to the left and this'll be a heading. But the size of the text will be small, since you know the titles of podcasts can be kinda long. This is gonna be episode.title and then there's an AccordionIcon, which the AccordionIcon it's not part of the Chakra UI icons, the other SVG icons that we use, it's kind of its own thing, 'cause you can see here it sort of flips around and indicates whether or not it's expanded or not. Okay, so that whole thing, that's the title, is the AccordionButton. Let's import these things, Accordion, AccordionItem, AccordionButton, heading Probably, missed something. It'll tell us what we missed. We missed AccordionIcon. Cool. Okay, oh, I'm duplicating. Oh yeah, then I've got the title here, which we don't need. Okay, cool. So this will show us the show notes. Let's add that next. So AccordionButton, the h2. And then if we look at our example, the next thing we want is an AccordionPanel. Yeah, let's add some padding there and some margin. And this AccordionPanel, so because if we look at, we have an example here. Yeah, so if we look at the summary, which are these are the show notes, some of these can be HTML. So we're gonna go ahead and render this as HTML. And we're gonna do that by calling the dangerouslySetInnerHTML prop here. It's called dangerouslySetInnerHTML, because this is basically React telling us that it's not going to sort of try to manage like state or virtual DOM for whatever we're setting here. That we're kind of on our own for managing that. So that's the AccordionPanel which is the, oop, which is we need to import. And while we're over here, I'm also gonna make sure that we, yes, let's get rid of this. So we had this main element that was importing some CSS, previously, and we had this sort of duplicate h1 here that we don't really need, because we have now the title in the header. So that looks a little cleaner. And cool, there's our show notes. And here's one and this is one is HTML. So this is a list. And so you can see here, like the links that are embedded show up as links that we can click. Cool, some of those are quite long. Some of them are not, right. So like this one is not, is not HTML. Cool, so I think that Accordion component worked out pretty nicely as just a way of showing show notes. Cool, so what's the next thing we want? So we have title of the episode. I think we probably need to show the title of the podcast. Let's bring in another Flexbox. So we need to show the title of the podcast to the date. And then along the bottom here, I wanna bring in the HTML5 audio component with the controls. So let's see, we can show the title of the podcast, which is episode.podcast.title. Oops, let's import text. Okay, there's the title. We don't really have any long titles, but one thing that is nice about the Text component in Chakra is we can add, this isTruncated and then that will truncate the text if it starts to bump up against the edge of its container. But it doesn't look like any of the podcast titles that we subscribe to are too long. Okay, then we can display the pubDate. So we'll have another Text element and here I'm going to use some template texts, so episode.pubDate.month slash episode.pubDate.day slash episode.pubDate.year? Day, month, year? Can it read property year? PubDate, not pubData. Cool, day, month, year. Let's add some margin on the left here for the title and the date. Scooch that over. Yeah, it looks better. Okay, we can also italicize the date. So we can say as i, so as an italicize element. Cool, so now I think we're ready for the audio controls down at the bottom here. So there is an HTML5 audio element which we can use like this. Let's wrap this in its own div and we can style that separately, maybe. But audio and then controls means we want to include the controls for playing, pausing, rewinding, this sort of thing. The source is gonna be episode.audio and the type is gonna be audio/mpeg, which I think is the standard for podcast feeds. So we can play a podcast. Let's see if this works. Well, I guess you can't hear it. I can hear it in my headphones. So we can do things like adjust the volume, fast forward, pause, all of that. Cool, so we have that. Let's add some additional styling here. Let's set, so I can't use the Chakra style props on the Audio component, but I can use the CSS in JS to set width to be 100%. So the audio control takes up the full 100% and then if we set up here marginTop, auto then that will bring the Audio component, so it's at the bottom of our Episode component. Cool, so that looks pretty good. I think that's functional. We can see the show notes. We can play the podcast and so on. One thing that's missing, though, so if we think of the typical user flow for a application is we can search for podcasts, we subscribe to podcasts and there are episode feeds showing us like all the most recent episodes of the podcasts we subscribe to. And maybe later, we'll add some more interesting personalization, maybe with some Graph Data Science features. But the playlist is, I think, a really important aspect. We haven't implemented this page yet, but basically we have this feed that you know if we subscribe to a lot of podcasts, this feed is just gonna be showing us a ton of stuff. And so what I like to do is for podcasts that I know I actually wanna listen to, I'll add those to a playlist. And then when I'm actually interested and like ready to listen to podcasts, I'll choose one of my playlists. So what we wanna do is, we said down below the show art we wanted to add sort of action item buttons. And I think that action item button we'll add there will be for adding podcasts to playlists. So let's see how we do that. Well, there is a GraphQL mutation to add episode to playlist. It takes, again, let's write this up properly. Just call this addToPlaylist and declare GraphQL variables. So addToPlaylist, let's declare an episode ID argument, or variable, that is of type ID and playlist name, which is required string. And then this is going to the addEpisodeToPlaylist mutation, takes the podcast ID, which will be this GraphQL variable that we're declaring here and it takes a name which is thisplaylistname variable that we declared here. Oh, and then we need, let's bring back you know the name of the playlist since we're just doing this from like an action button, I guess we don't really need any of the values in the selection set like, necessarily. But we can bring back the name, anyway. Okay, cool. So this now, let's declare as a GraphQL query in our Episode component. And we'll call this ADD_EPISODE_TO-PLAYLIST. I'll wrap that in the gql template tag. We need to import a few things here. We need to import gql and useMutation. So useMutation is a hook that Apollo Client provides us and down here in our Episode component, we can create the function to call from that hook. So addEpisode, the useMutation and pass in the query ADD_EPISODE_TO_PLAYLIST. So now this addEpisode, this is a function. This is an array because the useMutation, also returns things like the data which is the object represents the results of the mutation and loading and error if there were loading and error states, which we're kind of ignoring for now. So now this addEpisode function, now when I click that button to add the episode to a playlist will fire this addEpisode mutation. Okay, that's part of it what we need. We also need to know what are all the playlists. So and inside this Episode component, all we know is the data specific to the episode. So the parent component, we're going to need to also query for the playlists. So we need to know what playlists exist for this user. And then we will pass that as props to the Episode component as well. I think we can just freehand this. So playlists, name and, I guess, it's not just enough that we know the name of the playlist. So this button that we're gonna have on the episode, we're gonna click the button and then it's gonna be like a dropdown button. It's gonna show us, okay, here's all of the playlists that exist. You can choose one to assign the podcast to, but we also wanna be able to tell the user is this podcast already in this playlist? So for that, we need to also bring back the episodes in each playlist and bring back the ID, the ID of the episode in each playlist. So that means we also need to fetch the let's call this playlistData. So we'll execute this GraphQL query, when the component loads and then pass that to our Episode component. So it'll pass playlists will be playlistData.playlists. Okay, so now in our episode, we have episode and we have playlists. So let's take a look at Chakra UI. I think there is a Menu component I think is what we're interested in. Yeah, so something like this. So we'll have this button, we'll put this right below, oop, that went away. Let's log in again. We'll put this right below the show art is this button. We click the button and then it'll have all the lists of the playlist that we can assign this to. If the episode is already in this playlist, we'll have like a check mark and then we can choose what playlist we wanna assign it to. So let's import some things here. Let's import a Menu, a MenuButton, a MenuList and a MenuItem looks like the things that we need. And right here below our image is where we want that to go in the episode. So this is going to be a Menu. Let's give it some margin and we'll, again, set this to a fixed width, the same width as the podcast image. And then looking at the example, we need a MenuButton. So that's the thing we're gonna click. Give this also some margin. I don't know, probably don't need to set the width. So explicitly again for this one, we'll go ahead and do that. And we want this to render as a Button, because we're actually just gonna use an icon here. So this is gonna be the AddIcon. Let's import that. The AddIcon from chakra-ui/icons. Okay, so it's got the AddIcon, MenuButton. Now we need a MenuList. And here, if we have playlists, we're gonna map over the playlists array. And for each playlist, we're gonna return a MenuItem. So the MenuItem can have an icon. Let's look at an example. Here we go. So MenuItem can have an icon. And what we wanna do is if this episode is in the playlist for this MenuItem, then we want to render like a check mark icon or something that says, yep, this is in here. So we're gonna need some function. Let's call this isEpisodeInPlaylist, pass in the name of the playlist. So that function is gonna say yes or no, true or false, this episode is in the playlist. If it is, we'll set the MenuItem icon to a CheckIcon. Otherwise, we won't set an icon for this MenuItem. Let's import that CheckIcon. Okay, we also need to add a key prop, because we are mapping over an array and then we need an onClick handler. And here we're gonna call that addEpisode mutation function, but we need to pass in the variables. So we defined these GraphQL variables. So we need to specify, what did we call it? EpisodeiD, it's gonna be episode.id. And then we need the name of the playlist that we're adding the episode to which is v.name. Okay, so that's our MenuItem. And then the title is v.name, which is the name of the playlist. This will give us an error though, because we need to define this isEpisodeInPlaylist function. And for that, let's put that here isEpisodeInPlaylist. So that takes the playlistName and let's just return true for all of these, initially, just to make sure this works. What we'll need to do is figure out in our list of playlists that we've now passed in here, we'll need to figure out what playlist are we talking about and then check to see if the episode ID of the current episode is in that list of playlists, that list of the episode ID for that playlist. We need a button. Let's import that. Probably something else we forgot to import. Let's log in and find out. Nope. Okay, cool. So I have three playlists. But it's telling me, we're just returning true from this function, but that looks okay. That looks good, I think. So, let's write this isEpisodeInPlaylist function. So playlists, let's go to GraphQL Playground. So this is the query that we're running for playlists. So name and episodes and ID. So this playlists array, this is what we're working with here. That's this object, this prop and then we have playlistName. So what we wanna do is first filter the playlists array. So if we're looking for the must listen playlist, we wanna filter this playlist array for this object. So where the name is must listen. And then I think we'll map over the episodes array of the playlists and just select the ID from each one of, because these are objects. This episodes is an array of objects. So we'll select just the ID and then that will us just a flat array of strings and we can use the JavaScript array.includes, I think it is, to check to see if the episode ID of the podcast, sorry, of the episode that we're rendering, if that is included in the playlist that we are showing the MenuItem for. So, hopefully, that made sense. Let's see if we can do this. So let's say, so the playlist, the individual playlist we're interested in is playlists.filter and we're filtering where playlistName equals i.name. So i, this is each individual, I like to use single letter variables when we're doing these sort of map and filter functions. But okay, and then episodes. So now we're going to map over this episodes array of the playlist object that we just filtered and just select the ID. So playlist, this is an array but we were just interested in the first element of that array. And if it has episodes, let's map over that array and just return the ID of the episode. So now episodes is just a flat array of strings. So we should be able to return episodes includes episode.id. Remember episode, this is the prop that we're passing in to the Episode component. So this is the current episode that we're rendering. So if this array of episode IDs contains the ID of the episode we're rendering, we're gonna put a check mark in our MenuItem. So let's see. So this is not in any list, uh-oh. Hey, but this one is in all three. Okay. This episode, this is in all three. This is also in all three. It's very, very skewed. Apparently, our episodes are either all or nothing here. Let's, here let's try this. Let's create a new playlist, createPlaylist name. What should we call this playlist? Let's call it the, how about Breakfast time? So these are the episodes we're gonna listen to in the morning or something. Okay, create a new playlist Breakfast time. So now when we come back here, we may need, let's refresh since the data changed. Optimistic UI is another important thing for us to talk about. Wouldn't have helped us in this case, because we updated the data from, in a different case. Jennycat, feedme is the password. We should probably add some sort of loading spinner or something, but that's okay. So, cool. Now we have Breakfast time. But for some reason, these don't show up. Let's test the mutation. So we'll check Breakfast time. Now, if we go back to playlists, name, episodes, just the title. So here's Breakfast time and here's the episode that we just added to it. Great, but why didn't this work to show us? Oh, this looks a little funky. Something, I feel like something changed here. Return playlistName equals, equals, equals. Yeah, there we go. Okay, so let's add this episode of "The California Report". Let's add this to Breakfast time. If we go back, bam. Okay, now we have two episodes in Breakfast time, the episode we just added. But you'll notice this didn't update in our, so we didn't update to add the check mark here when we clicked the button. And if we refresh, do I have to sign in again? Which, again, this is another thing we need to fix. But now, yep, now we can see. Yep, we've added it. So here we can add it to must listen. Let's go to the podcast and then come back, so I don't have to sign in again, just to trigger rerender. Cool, yep, that's in there now. So the next thing we wanna tackle is dealing with what's called Optimistic UI where we actually want to immediately, as soon as I click this button, so here we're going to add this to the jogging playlist. We want to immediately add that check mark, even before like the mutation has occurred and gotten a response from the server. 'Cause we're like, optimistically, updating that data instead of having to go back and refresh the data to see that we've added it to the playlist. So that's the next thing that we wanna tackle. But I think we'll save that for a future episode since we've been going for, has it been almost two hours now, already? Wow, that went by really fast. So we created this Episode component and we're now able to view the details for episodes in our feed. We can assign them to playlists and we have this nice now reusable Episode component. So we'll be able to reuse this, say when we're showing lists of episodes in a playlist and so on. We also introduced Chakra UI as the React Component Library that we're using to build these components for our podcast app. So, cool. Hopefully, that was useful. In next week's episode, we will pick up with Optimistic UI and then I wanna implement the rest of the views here. So we want to do playlists, so that we can show details for all the playlists you have, create a new playlist and then add search so that I can search for podcasts and subscribe to podcasts. So we will pick that up next time. The code for all of this lives at GRANDcast.FM, currently is the URL, but that just redirects to the GitHub page. At some point we'll deploy this and then I'll put it actually on that URL. But the README here has all of the previous recordings. And then I also, I like to just push all of the commits up at once or, yeah, do one commit per episode. So you can sort of see here's everything that we added last time. So I'll update the README after this episode with the code from today. Cool, well, thanks a lot for joining and hope to see you next time. Cheers.
Subscribe To Will's Newsletter
Want to know when the next blog post or video is published? Subscribe now!