Current section: Client Router 27 exercises
solution

Client Router

Loading solution

Transcript

00:00 Let's go into our UIIndex file here, and we've refactored a couple of things to make some things a little bit easier to use for what we're doing now. And right now we have our initial location and initial content promise. These are the initial values for when we land on the page, but we want to be able to update these, and so we're going to stick both of these into state.

00:19 So our location is going to come from useState. We'll pull that from React, pass our initial location, and now location and setLocation is what we've got there. And then we also need to be able to update the content on the screen, so this is also going to be a useState for initial content promise, and this will be our contentPromise

00:39 and setContentPromise. And with just that change, nothing should actually impact the functionality of the app. Everything is still working, but now we can update those values. So when navigate is called, we can update the location. We're going to need this navigate function to accept the next location we want to go

00:59 to, nextLocation, and then we'll set the location to the next location. And then we're actually going to do something kind of similar to what we're doing right over here. So we create from fetch. We fetch the content with our initial location, so let's just do that, but we're going to do it in a couple pieces to make it a little simpler to follow.

01:19 So we're going to get a nextContentFetchPromise. So we're going to fetch content at our next location. And so, again, this is going to end up being – where's our fetch content? Here we go. So here's our location. It's going to end up being a fetch request to slash RSC with the current location.

01:38 So if we go to RSC, then here's the RSC content here. If we change the location to be galaxy, then it's going to change our search results. So now, with that promise, we need to convert the response of that into content that can be displayed.

01:57 So we'll say nextContentFetchPromise. We'll pass that into createFromFetch. That turns it into UI, and that's going to be the content. So if we say setContentPromise to that – and actually, let's make this a variable.

02:16 So nextContentPromise comes from that. And then we set our content promise to the nextContentPromise. NextContentPromise – there we go. Now we actually – it should work when we call this navigate function. So we're almost there. If you click on this, it's going to do a full page refresh.

02:34 And that's because that's the default behavior of the browser is to do a full page refresh. But that's not what we want to do in our case. We want to prevent that default behavior. And if we dive into our router here, we've got this special useLinkHandler that accepts a navigate function.

02:50 And whenever a link is clicked on in the proper situation, then that navigate function is going to get called. And this adds that event handler to the entire document. So any link on the page is going to call navigate when we do this.

03:04 So let's take that useLinkHandler and apply that with our navigate function. So now instead of the default behavior, it's going to override that default behavior. So if now I click on this – oh, I've got to refresh the page first.

03:21 Now we click on this and we're still getting a suspense boundary. It looks like a full page refresh. But you'll notice in the favicon in the top corner, it's actually not doing a full page refresh. Even though we're rendering our suspense boundaries and everything, we're not doing a full page

03:35 refresh because we are overriding the default behavior. Now the question is, why does it fall back to the suspense boundary? It doesn't look good at all. And that is because we're not doing this in a transition. So let's do a transition.

03:51 Start transition from React, which actually we incidentally already have imported from React. We'll start this transition like so. And now, because it's a part of transition, React is like, oh, okay, then we don't need to show the fallbacks for the suspense boundaries.

04:10 We'll just reuse the ones that we had before. And that is working just fine. We can also take a look at the fact that we're not updating the URL as another problem we've got to address. So let's take care of that. Because if I go to the dreadnought and then I refresh – oh, that was the ID we were on.

04:28 If I go to Fargate now, or Frygate, whatever this one's called, then that's not going to get us back there when I do a refresh. So we need to update the URL. So what we're going to do is when this – actually, we'll update this. So we're done with the location.

04:44 We've created the next content from Fetch, which is from FetchContent. And we want to add a then handler to that. So we'll say next content FetchPromise.then. And this will be our response.

05:02 Here we're going to say if – well, here, let's actually start by returning the response. You don't want to mess that up. So it's good to just start with the response. So when the next content promise resolves, we can simply say window.history.pushState. And the first argument is any data that you want to provide. We're not going to provide any right now.

05:20 The next argument, it even says unused. It is technically – I think it's title, but yeah, nobody ever uses this. And I think it's probably ignored these days. You could take a look at the MDM reference there. And then our final is the thing that we're actually trying to do, which is where are we going? So we're going to go to the next location.

05:40 So with that change right there, now I can click here and you'll notice the URL gets updated as we're navigating around. So refreshing the page will put me right back where I was – where I wanted to go. So that takes care of our link clicks.

05:54 But if we go over to our search right here, then this is a client component because we want to handle the onChange event or the event of the input. So as the user is typing in, we want to handle that.

06:11 This useClient is also required for our useRouter because if we look at our router, that router is exposed via context and you can only use context in the context of a client component. So let's grab our location and the navigate function from our useRouter.

06:29 And with that, we'll add a submit handler here to prevent the default behavior of a full page refresh. And with that, we don't need to even worry about the submit event because we're going to be handling changes as the user is changing things.

06:46 So we'll say right here onChange and we'll take that event. Here's our new location. I always like to say current target so that works just fine. We're going to merge our location state with the current location. That'll give us our new location with this search overridden.

07:03 And then we'll navigate to that new location. And right here, our AI assistant is helping us along saying, hey, you want to do replace true. And we'll see why here in a second. So I'm going to get rid of that. And let's go to a brand new instance of this page. So there's no history going on in here.

07:21 We're getting an error here. So let's take a look at... Oh, useRouter is not defined. That's easy. Easy peasy lemon squeezy right there. There we go. Okay, great. So we have no history and I can type in here and it should update my content. Oh, but it is not. So let's see if we're getting any errors.

07:41 Merge location state not defined. Okay, fine. One more import. There we go. This is why we use a bunch of tools, right? I am a fan of TypeScript. Okay, great. So you'll notice our URL is getting updated and our UI is getting updated.

07:57 So exactly what we want, but we started with no entries in the history and now we've got a bunch and that is not awesome. So this is why we want to do replace true because there are certain UI experiences where

08:12 it makes sense to have an entry in the history call stack or the history stack and others where it doesn't make sense for everyone to have an entry. So we're going to replace the current entry with the one that we're pushing into the stack. So we're just going to replace that, the current entry.

08:30 So if we go into our router, which is going to be in our UI index, we're going to add an option here for replace. We'll default it to false. And if you want to replace, then we will do things differently.

08:44 So if replace, then we'll do one thing, otherwise we'll go to push state. So we're going to history replace state. And now we're not going to have the problem that we had where we're adding a bunch of entries to the history stack.

08:59 So coming back here to make sure we've got replace true, we'll save that. And I will open this up in a brand new instance of our tab that doesn't have any history in it. And I'm going to type a little bit in here. And you'll notice we're not updating the history stack, so that's exactly what we want.

09:19 If I go back here and we'll click on this one, we are adding an item to the history stack. Again, we're not handling the pop state yet, so that's why this isn't working. But if I hit refresh here, then you'll notice we've got that here.

09:33 And if I were to say star, and then we go forward and backward, now star is showing up in there because we replaced that entry in the history. Okay, quite a bit of things going on in here. We're still not actually finished.

09:48 There's one more thing we want to handle, and that is when we select a link, we want to make sure that we are, or rather, I should say that we are refactoring this because it works as it is today, but we can make the experience a little bit better.

10:04 Right now, our select star ship link has to accept the search so that it can do a manual merge. Now, we can get that search from the router. So let's get our location from use router, and we can get rid of all this stuff right here.

10:23 And href can now be merge location state with the location. And we no longer need to worry about the search that'll be merged or handled automatically because it's already stored in the location. It's just the ship ID that we need to update. And great. So now the select ship link no longer needs to accept the search.

10:42 So that is neat. Let's just come over here and make sure that is working. If I click on this, our search should be preserved, and it is. So everything is working the way that it should. Let's just quickly review our navigate function because that was the most important part of what we did in this exercise.

11:00 So aside from putting our location and our content promise in state so that it can be changed over time, we also implemented our navigate function. This accepts the next location. So we update the current location to be the next location. We start fetching some content. So this gives us our next content fetch promise.

11:19 And inside of this, we're going to determine if we want to replace state or push state. So once this promise resolves and the response starts streaming in, we can update the URL to be that location. And then we're going to convert the RSC payload that is coming from our server into React

11:38 Elements, and that's where we get our new content promise. We set the content promise to that new content promise. That triggers a re-render, and then we come down here and we use that content promise. And that is our initial implementation of this router.