The video above is a live-coding screencast where we debug some annoying client-side routing edge cases and user experience problems in a React application using React Router.
Find the code repo used in the screencast here
Transcript of the talk
[00:00:00] Hi, everybody. This is Tommy Groschan from Test Double. And I'm coming at you today to talk a little bit about user experience. All right, a little bit about user experience, a little bit about React, and a little bit about client side routing, browser history, all of that fun stuff. All right, so I'd like to start off by giving you a real big disclaimer.
[00:00:24] I am not a great UX designer. I'm not a great designer, period. I am not Simply a humble backend coder who tumbled and fell into the front end world. And I'm just trying to make the best of it. Now, that being said, I have a not unique, not very unique gift of being able to tell when a user experience is particularly, All right.
[00:00:48] I think most of us users of the internet are pretty good at that. So I'd like to show you a couple really poor user experiences that I see all the time that involve client side routing, especially when it comes to the URL and history management. All right. And I'd like to start this whole thing off by saying.
[00:01:09] Don't break the web. All right. All right. Now let's go over some ways that you might be breaking the web and some ways to not do that anymore. Okay. So I have for you today a beautifully crafted little piece of code, a little application for you. Organically grown, you might say, but okay, no, that doesn't make any sense, but whatever.
[00:01:32] Okay. But I've got for you a little bit of a little bit of code. Okay. This is our little example app that we'll be using today. All right. Recently, I have been doing a whole bunch of stuff. On helping family and friends building computers and build a couple for myself lately. And if you're ever interested in that kind of stuff, please hit me up on Twitter.
[00:01:55] I love to talk about this stuff. I find it just interesting, even though I'm still very much a novice in a lot of ways. So I've been looking at a lot of this kind of stuff, and these are some things that are totally not just a product and images ripped off of Newegg. But if they were, this would be a pretty fair little.
[00:02:13] product store for computer parts. Okay. And right now it's pretty simple. All right. It loads pretty fast. All right. Look at that. There it goes. All right. You can click on things. You can get some details, the price, you can add it to cart, which totally works. As you can tell, totally works.
[00:02:30] And you can go back to where you were and click on all kinds of things you can filter now. I think you'll admit that as examples go, this is pretty amazing, okay? And, here's the great part here. When you do a filter, and then you click back, Oh no! Here we go, we'll do that a little slower.
[00:02:53] Okay, I'm on the details page, I just came after filtering some stuff, and I'm gonna click here, back. Boom. Eww. What just happened? Yucky. Okay. What if I hit the back button? Okay. Hey, my filters are there again. Okay. And what do I mean by that? Okay. Forward one. That's our product page. Forward two. This is the page that I got sent to.
[00:03:19] How'd that happen? How did that happen? You see this all the time, you see this all the time, and stores online stores are a great example of this because, one, we've all used them before, we all know we all have expectations when we come to them, and they are great examples of this particular problem, and that is first of all, okay, one of these problems we've already solved, okay, a lot of places, when you're doing single page applications, they won't even be synchronizing your, uh, filter state to the URL.
[00:03:50] And now, why is that a problem? Because if I were to refresh, if I were, if we weren't synchronizing things, then all my filters would go away. But that's not a problem here, alright? Because we are synchronizing all of our filter state. And, oh yeah, by the way, you can sort it by price. Ah, there we go. Very cool.
[00:04:08] Not sort, filter. There's, that's the right word. Now, you can, a quick aside about, URLs versus local storage. Okay. You can solve both. You can solve this problem with either the URL or local storage by taking your local react state. Let's take a look at the product page here, right? Here's my filters and the do work function that does the filtering.
[00:04:35] Huzzah. And then here's the side effect, the react hook effect that is doing the work of synchronizing the filter state. somewhere else. All right. Right now we're synchronizing it to query params. This is magic. Just trust that it works. But you could do the same thing if the, if it weren't setting query params, but we were setting something in local storage.
[00:04:58] Okay. Now why would you choose one over the other? Okay. Assume they're basically both. Generally interchangeable for this kind of a use case. Why would you choose one or over the other? URLs, please Synchronized state to the URL if it's something that makes sense to bookmark or if it's something that makes sense share with somebody else put in the URL, all right, and Sometimes you may not even know sometimes it's good to pick the URL first even if you're not really sure how something's going to be used, sometimes it can be nice to pick the URL as your state synchronized target.
[00:05:37] To pick that first, because you may not know the shareability of some particular page or some particular element of your site, but your users may find a use for it. Okay? So for that kind of, for that reason, whenever you're doing something where I've synchronized this to the URL, so if I were to send this to a friend, they see the page that I was on.
[00:06:05] All right? They see the filter state that I was on. All right? That is a good thing. Okay? So that is a very clear example of where You want to synchronize some state to the URL rather than to just local storage. Okay. And rather than not synchronizing it at all somewhere. Okay. So please do whenever it comes to sorting lists of things, it's almost always best to put in the URL.
[00:06:29] Okay. So now we've already done that. Okay. Let's say we were great devs. We already did that. Okay. Hooray. Now there's a problem here. All right. And let's take a look at what happens when we click it and then. Click on a product. We then click the back button. What happens here? We lost all of our URL. We lost all of our filters and okay.
[00:06:51] Go back. That's there. If I go back again, that's there. So that thing was in the browser history, but Yeah, when I click the back button, filters are gone, that's annoying and yeah, it just so happens that QA is all over us because users are complaining about this. We better fix it. We better fix this right fast.
[00:07:13] Okay, let's take a look at why this might be broken. Okay. Let's get back into a filter state. All right. We're on the products page. Okay. On a product three, we would be going to here and that's the product details page, which is right here. Okay. Okay. Okay. Very run of the mill run of the mill. Yep. Yep.
[00:07:32] Beautiful. No, not found sign backlink. Yeah. That seems about right. Yep. Okay. Let's go. Here we go. Here's a backlink and there be our problem right there. We're just doing a very naive link back to just the bare the bare URL, the bare path, whatever that's called. There we go. Slash.
[00:07:51] We're linking just, we're just linking back to the forward slash, back to the forward slash, and that's stripping all of our query string away. And thus all of our filters, but we've already established that the back button works. Okay. The back button works. So what can we do with that then?
[00:08:10] What if we instead, let's just emulate clicking the back button instead. Hey, big brain time. Let's do that. So we're going to bring in the history hook. Great job to the react router team for their hooks API. I enjoy it very much. Very much great job, Ryan, Mike, and friends. So let's turn this thing to a button instead of a link.
[00:08:36] It's tailwind. And. Instead of doing that, let's do an on CL click with sane person stuff. Sane person casing. Yep. There we go. And handle, click. And what do we wanna do on that? We want to do something like history dot go back,
[00:09:02] save that. Okay. Thank you. Prettier. And now what happens if we go back? Oh my goodness. Peace. Be still my soul. How just amazing is that? Okay. You might think it's real amazing, but I'm sorry, my friends, this is not great. Okay. This has a very a very some, a handful of issues here. Let's do a very simple one.
[00:09:29] Okay. I take this product page and I'm going to brag to a friend that I'm about to buy a 16 core 3950 X like a boss. Okay. And I send that to my friend clicks on it and they're like, Whoa, man, that is just crazy. This is a cool site. I want to see what other products they have here and what just happened.
[00:09:50] Oh my goodness Where am I? What's going on here? I just want to see the rest of the product. Where's the rest? Where's the rest of the products? Oh my goodness because obviously when we Open the link when we on a tab where we don't have history, the back button doesn't do the right thing at all. It gets worse, everybody.
[00:10:11] Let's say that you didn't send this to a friend. Okay. All right. We have history here and everything like that. What if instead of that, what if we had again, another big brain moment? Okay. We're power users. Okay. What if instead of clicking on the thing like a peon, what if instead we, Oh, open link a new tab?
[00:10:28] No one does this, right? Okay. Wait a minute. What? What? Nothing works. Nothing works. Okay. Okay. Yeah, sure. Like this one is a little, is not quite as terrible because hey, you open a new tab, you still have your old tab, you can go back to it. But wouldn't you expect to still actually be able to use the site?
[00:10:51] From this point on, that seems pretty reasonable to me. Okay. So that's a problem, or let's just say, here we go. Let's just say that we're awesome. And we we click on the thing here and we do something totally uncharacteristic of a very average browser user, but we click the bookmark button.
[00:11:11] Okay. We're going to save here the 3950x great price. Okay, so here we go And I'm off doing something else. Let's say that I am over at Newegg Okay, or I send this link to a friend or whatever right now. We're doing bookmarks Okay, I'm over at Newegg and I'm looking at over here at the 3950x.
[00:11:33] Okay. Okay. Okay. Oh, very cool. Very cool. Oh, yeah, you know what here let's Let's take a look at this here. Okay. Okay. Yep. Okay. I remember that. Okay, basically the same price. Okay. Did we have what did they have for the other CPUs? What? What just happened? What? What? What? What?
[00:11:53] Wait a minute. Okay, so I'm here. I click the back button. What? That's not at all what I wanted. Okay? I guarantee you that if your marketing department catches wind of this particular little user experience, someone's gonna flip their lid. Someone will have a stroke, okay? Some marketing person is gonna be rushed to the hospital the next moment, okay?
[00:12:20] Don't let them find out about this. Okay. If they find out that you are redirecting your users to a potential competitor's site, whole marketing department heads explode. Okay. So don't do it. Okay. Don't do it. What are we going to do about this? What will we do about this? The way that we can do this is what do we want?
[00:12:38] What's our goal here? Our goal here is that if somebody opens This page navigates to this page from the products page with potentially with filters or anything like that. We want to be able to send them back to that same state, the same URL. And we want to be able to do that, right? Makes sense. But not only that.
[00:13:03] We also need to be able to handle if someone comes to that page directly without navigating to it. And that's something that can be really tricky to plan for. But we need to plan for that because this is the web, all right? This isn't an app where, a person has to open the app and they have to go through a very specific, set of, of swipes and taps in order to get to some state.
[00:13:24] No. This is the web. People can deep link to pretty much anything. All right. So we need to be able to support those two cases. All right. Being able to support the person who, the the persistent user who is here already and wants to go back to continue that workflow. But we also want to be able to support that user who Came here in a new tab or came here in a bookmark or came here from a link clicked that was sent to them over email.
[00:13:54] Okay. So we need to handle both of those cases. So there's a trick. We have a trick up our sleeve. Okay. And that is we're using the history, the new history API for browsers. That's what react router is wrapping. Okay. And they're specifically using some things. Some API that they're calling push state and replace state.
[00:14:19] All right. Now we have this history object and you can hit back and hit forward. You can hit go. What does go do? Oh, you can go to a, give it a number and you can jump to. Some of us here, Hey, that's pretty cool. That's pretty cool. But what we care about right now is push state and replace state.
[00:14:34] Okay. Now what this means is whenever you're using the push state API is each time you call it, you're giving it. Yep. It says right here, a new history entry is being created. History entry is what shows up in here. If you click and hold, you can see your history for this tab. And every time you call history, push state.
[00:14:56] You can give it a state object, a title, and a URL. Okay. If you don't give it a URL, the URL is funnily enough optional, because if you don't give it a URL, there's going to take the current URL as the browser entry. Okay. Title is something that they say it's maybe used in the future, but people, browsers basically ignore it right now.
[00:15:15] It's just a string. Title. State object though. Now what is that? State object is associated with the new history entry created by the call and whenever the user navigates to the new state a pop state event is fired and the state property of the event contains a copy of the history entry's state object.
[00:15:36] Now, what does that mean to a normal human? That means that whenever you're navigating through the history, that's a pop state. Okay. That's maybe a push state. I don't know. Maybe that's a pop state. I don't know what that is. Okay. But whenever you're navigating through the browser history, the browser is not just grabbing the URL.
[00:15:56] From the history entry. It's also grabbing this state object, okay? And state object is really anything. It's anything that we want to, that we want to put there. It's any serializable object, okay? And that means that you can optionally just stick this JavaScript object that's always gonna follow that that's always gonna stick with that Browser entry.
[00:16:17] That browser history entry. So we could use that to our advantage because guess what? History. All right. When you do a history, let's say we want to do something instead here. History dot push. Push takes two things here because the history A in react router is really just this a nice ergonomic wrapper around the browser history API.
[00:16:40] So they have a push with a path and an optional state object. How exciting is that? So what we could do here, we can use this to our advantage here is how do we get to, first, we think, how do we get to. This product page. We click on one of those product cards. Okay. So let's go to the products page.
[00:17:00] Here we go down here at the bottom. Yep. Product card. There we go. Link to, there we go. Yep. Very vanilla. Very simple. Now there's another way we can handle this. Okay. Now this is, that's the same thing. All right. That's the same thing in a fancy, different way. All right, you can, the link component takes two and it will either be a string or a location descriptor object.
[00:17:28] What a name. What a name guys. Okay. Location descriptor object, right? The location descriptor object can include a path name and it can also include ding, ding, a state object. Okay. How exciting is that? Now, what are we going to stick here? Let's just do something very simple here. We'll send the return URL, right?
[00:17:50] What do we want the return URL to be? We want it to be where we are right now. So let's get the current location. Use location. There we go. Oh, better import it. Use location. Okay, here we go. And yeah, here we go. We want the location. We want the path name. That makes sense. And we want that. We can't forget the query string.
[00:18:15] Okay. We want that the search string. Okay. Query string. There we go. So we get that. Okay. That's very good. All, all good. All good. Okay. So now what this should do is if all things go well, Here we go. Let's get our location. Let's change this back. There we go. Okay. Use location. Now. Better import it. Use location.
[00:18:46] Our friend. Now. What's this location? I'm always used to not spelling that like that. Oh. Pain. Okay. Let's just see what the, let's just take a look. See what the location looks like. Object has got for us. Okay, we open up the console. We click a thing. All right, there we go We get this location object and it has ooh Look at that our return URL.
[00:19:11] Okay, and Hooray. So we can use that to our advantage. Let's do it. All right. Now what we're going to do is instead of this whole go back business, what we want is if the location state return URL exists, because this is important to know if you didn't explicitly pass a state object that then it's null.
[00:19:36] So you need to make sure that you Do a whole check for any undefined or null. Okay. Okay. It's not null. It's undefined because this is JavaScript. There we go. Okay. And if we have a return URL then we want to push to it. We want to return to that return URL. And if we don't have a return URL, let's just do what we were doing the very first time we were trying this.
[00:20:02] Okay. We'll just go back to the bear. Forward slash, okay? Let's give that a shot, okay? Whoa okay. Oh yeah. Okay. So the go back part still works. Now the moment of truth, open a new tab, click the back button and we go back. Hooray. We have now handled both cases, but don't take my word for it. Oh, we click.
[00:20:33] Here we go. Here we go. Let's go to new egg. There we go. We click this. Click our bookmark. We hit the back button. Boom. We stay here. We don't go back to some random site. We don't just automatically say, Hey, we'll just send you back to wherever the hell you came from. All right. So we've just handled both of those things.
[00:20:54] So what I'd like you to take away from this. Is that one, you really want to use the URL as best you can. You want to take advantage of it. It's a powerful tool and it's something that is very it's unique to working on the web, all right. Desktop apps and mobile apps don't have this same, just out of the box URL behavior where people can just deep, deep link to any old spot in your site.
[00:21:20] Give them that option. Give them the ability to use bookmarks. Give them the ability to to share links with their friends. You can reap a lot of really great benefits from letting, supporting the user using the web. So let them do that, but definitely make sure that you. You give thought to the URL, the routing behavior, and the experience that the user is going to, that the user is going to go through.
[00:21:48] Okay, you want to give this thought. And that's the thing. None of this was very hard. This really only took, okay, eight lines of code, something like that. And then this, here we go. Product page. Yep. We just made, we just had a very, a slightly more complicated instead of a string. It's a little more complicated object.
[00:22:08] Okay. Not that hard. Okay. That's not a hard, that is not a hard fix, but it has a lot of meaning because. Without that for certain users, the site would just, the experience would just be straight broken. Okay. Whether you were trying, whether you're already a user actively engaged in the site, and then you got thrown off because something just didn't work correctly, or you were a new user or you came here a returning user or something like that, and just the back button didn't work at all.
[00:22:40] Okay. Now I would like to just shout out this a little bit. I think this is also a terrible UI and I actually did this on purpose. I did, I called this back and put an arrow on it because I've seen that button in almost every large enterprise app that I've worked on. Okay. Enterprises, it makes it sound like it's these weird companies.
[00:23:01] No. Very well known very well known companies with very talented developers, and I've seen this. This user this UI element all over the place, a back button. It's I have a back button right there. Thank you. And that, and especially when I've also seen the back button, that was literally just a history dot back his history dot go back over here.
[00:23:25] And it's important to realize how many ways just a vanilla go back call can break. All right. And. Really what we're wanting to do most of the time is we're just wanting to support the user being able to come to a spot in multiple ways, being able to come to a specific page, a specific state of that page in multiple different ways.
[00:23:50] So if we can support that, we can enable our users to do so much more. So that's really all I had for you today. So thank you very much for tuning in for this short little screencast. I really appreciate it. And again, this is Tommy Groshan from Test Double, and I'll talk to you later. Bye.