Use nested dynamic router segments along with the Next.js Link Component to tie our channel links to new URLs. Learn why you should separate out UI states in order to avoid CSS conflicts.
The code for this lesson is divided into two sections, with this you can see the progress on the code.
Instructor: [0:00] To style the active and inactive states of these channel links, we'll need the links themselves to actually change the URL. Right now, in our clone, the only links we have are these server links which change this part of the location.
[0:15] We want to introduce another dynamic segment to our URL hierarchy so that when we click on these channels, we get another param right up here. Ultimately, we want this URL to look something like /server/1/channel/1. That way, we can navigate two different channels.
[0:31] The way we do this nested routing in Next is to nest these dynamic segments. What we're going to do is put this page under a folder called SID and then channels. Then we'll call this page CID.js for channel ID. We'll see that VS code updated our import path.
[0:52] If we save this and then visit this new URL, we'll see we have our page rendered again. This is how it works in Next.
[1:03] Every time we visit this URL, we'll have access to both of these dynamic segments via the SID and CID router params. Let's close the sidebar and turn off wrapping here and come down to our channel link.
[1:18] To make these actually link to their respective channels, we need to wrap this anchor tag in a link tag. For this link, we can go ahead and import this from Next link. We can drop the key here. We didn't need that. We'll also remove the HRef and put it on this link tag.
[1:39] This will be an expression to something like server/1/channel/1. Now we can click these and we'll see it's actually rendering a link here which we can tell because Chrome is showing us the new URL. Of course, we want this channel to be dynamic.
[1:56] We'll just throw in our channel.ID that we're getting from this channel link prop right here. With any luck, we can click on these different channels and we can see in fact, the URL is changing. We can use the Back button and change that as well.
[2:12] This is how we'll be able to conditionally style these links based on whether they're active or not. Over in Discord, we can see the style changes when it's active, the text becomes brighter, and we have this background. We just need to know whether the link is active.
[2:27] We'll want to use our new CID variable, which in this case, is four. We can get it in the general case by saying, let router = useRouter, which we can import from next router. Then we can say, this channel link is active if the channel's ID is equal to the router.query.channel ID.
[2:51] We can come down to our anchor tag. Make this an expression. We'll just drop a conditional right here. If it's active, we'll use these classes. Otherwise, we'll use those. Over in Discord, we can see when the link is active, this text becomes pure white. We can see it right there.
[3:18] Let's say, if we're active, the text should be white. Otherwise, the text should be this gray 300. It doesn't seem like our active class is working. If we pop open the console, we can go ahead and log out the channel and we'll get the router.query.
[3:40] If we look, we'll see the channel has an ID of six. That's coming from the data that's generating the sidebar, and the router is giving us our CID and SID here. We'll see that it changes as we navigate.
[3:54] These are coming in as strings because any dynamic segment from a URL is a string, but our ID here is a number. When we're checking for active, let's just coerce these both to numbers. There we go. We see our active state. When a link is active, we also have this background.
[4:16] Let's take a look at this. If we check this out, we'll see it's 4F5, which is the gray 550 that we've been using, but the opacity level here is at 32. If we're active, we want the text to be white and the background to be gray 550 with a 32 percent opacity. Let's see if that works.
[4:39] Looks great. We still have the hover treatment in the base case, but we don't have any hover when it's active. We'll just grab these and move them to the inactive part of our conditional. Now we can see inactive, with the hover treatment, and then active.
[5:00] It's all tied to the URL. We can swipe back and that active state follows it around. We can use these and it all works. One thing that we broke is the active state on these server selectors. Let's go take a look at _app.
[5:16] Right now, these are checking for the path of the URL and seeing whether it matches what we pass in as the HRef. First, we can update this and just add channel/1 here.
[5:28] That way, when we click on this, we'll at least get to our good URL. As soon as we click off of channel one, we'll see that that active state goes away. We're still using the nav link for our Home button. We do want that to check against the HRef.
[5:44] This is correct for that. For these, we can just add a new prop here to give ourselves a little more control over when these are active. For this we'll say, it's active if this server ID parameter in the URL matches the server ID that we're iterating over.
[6:00] Again, if we look over here, we'll see that that just comes from this folder right here, which means we need the router again. We can just say, let router = useRouter. Then it's active if the router.query.SID = the server ID.
[6:18] Again, we'll go ahead and coerce both of these to numbers when we do that check. Let's save that, close our sidebar. Now that we're passing in active, we can come down here, expose this new prop. We'll say that active = active.
[6:40] If the caller's passed it in, we'll just use that or we'll just fall back to our default check where we compare the path and the HRef. We can actually use the new or assignment operator to do the same thing here. Let's update these to use active.
[6:59] There we see our active state is working again. We're in a good spot with the active state of our channel links. Everything's tied to the URL, like it should be, and this is working great.
[7:20] One point I want to make here is that when you start adding dynamic behavior like this, where you're using variables and building up a big class name, is to make sure that your utilities don't ever conflict with each other.
[7:33] If you were just starting this out, we had text gray 300 on the base string right here, and then you come and add active. Then maybe you say, "Oh, we want to make this text white. Otherwise, do something else."
[7:46] Your UI might actually work. When it's active, the text might go white. You'll notice that you'll be applying two text classes at the same time and this can lead to unpredictable behavior. Utilities are really best used when they don't conflict or overlap.
[8:03] It leads to much more predictable code because there's only one class that can be responsible for any given CSS property. If we remove this expression right here, and I were to add text gray 300 and text white like this, and save it.
[8:18] You'll actually see that the Tailwind Intellisense will give us an error, and it says there's a CSS conflict. Text white applies the same CSS properties as text gray 300.
[8:30] Especially when it comes to things like text colors or hover treatments, once you add more dynamic behavior, you really want to pull apart the different states and only apply the classes you need for each state.
[8:44] This is especially true when the states of our UI component increase to three or four or five. This is just a hard one lesson that I want to make sure and point out. It'll save you lots of time and make your code a lot more maintainable when using utility classes.