skip to content

React Router 6.4

The latest version of React Router introduces powerful new patterns including data loading, mutations, and pending/skeleton UIs. Ryan Florence will teach us what's new and how we can use it in our own apps.

Full Transcript

Click to toggle the visibility of the transcript

Captions provided by White Coat Captioning ( Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.

JASON: Hello, everyone, and welcome to another episode of two beards, two hats, two hoodies. (Laughter). I'm your host Jason. Today with me is Ryan Florence. Ryan, how you doing?

RYAN: I am doing well. I'm a little stuffy. My kids went back to school. So everyone is sick now, right?

JASON: Yeah, I think that's the way it goes. Geez. Well, yeah, so I'm thrilled to have you back on the show. I think this is going to be a whole lot of fun. For folks who maybe aren't familiar with your work, do you want to give us a bit of a background before we jump in?

RYAN: Sure. For this livestream, React Router. So, created it with my partner Michael Jackson, business partner. Eight years ago. I can't believe we've been working on that thing for almost a decade. But I mean, if you're watching this show, you've probably heard of React Router. So I'll just leave it at that, I guess.

JASON: And then I think, you know, it's safe to assume that people have probably come across your work in the past, but you created React Router. I mean, you've been all over the place. Maybe even just a super high level of your kind of path to today would be super useful.

RYAN: Sure, yeah, yeah. Geez. I started web dev when web dev started. I'm 41, I think. Maybe I'm 42. I'd have to do the math. I honestly at this moment don't know. I was born in '81. Maybe someone in the comments can tell me how old I am. Anyway, so the internet comes out, and my dad invested in ISP. Remember those? You had to like plug into the phone line and call up and do all the modem noises. Anyway, and so I got into web dev. Back then, you just had your own FTP server and could stick files there and you had a website. That's when I got started, just HTML and CGI. Then I remember when CSS was invented. I started using CSS. Then I remember when J-script and JavaScript showed up on the scene. Then dynamic HTML. Then the Ajax generation. Honestly, I kind of hit my -- I started making websites for my punk rock band. That's why I got into it. The popular bands in Utah would let me play shows with them if I promised to build them a website. So that's how we got shows. Then I kind of -- I didn't do a whole lot with web development for a couple years. Did some volunteer work for my church for a couple years. And then came back and then got into college and didn't do a whole lot. Then I got married, and my wife needed either a -- she was studying photography, so she needed a portfolio. And we could either print one, and that was going to be like 300 bucks. We were just super poor college kids. So that wasn't going to happen. But the teacher is like, you could also make a website if you know how. So my wife is like, didn't you know how to make websites when you were a kid? I was like, oh, crap. Kind of. So I got back into it, and she'd go to websites on other people's portfolios and be like, hey, can you make it change colors when they switch pictures? No, I don't know how to do that. Then the next day I'm like, hey, come look at your website. Look what I did. So I really got into JavaScript and really dynamic websites in the early 2000s. And that was the prototype, Mu Tools, jQuery. So that's when I really cut my teeth in JavaScript. Then after that, I did some backbone and jQuery together. Then I got into Ember. Loved Ember. That's where we learned nested routing. If you read through our features page on the React Router doc side, we have a whole paragraph that's like talking about how Ember taught us about these nested routes and how cool they are. Then React hit the scene, and I jumped on the wagon with everybody else. And here we are. Oh, and I built Remix. Yeah.

JASON: Yeah, then built just that little old thing.

RYAN: That little old thing. Yeah, been working on that for a couple years. And they had some really cool data abstractions, which I guess brings us to today, where we discovered some cool stuff in Remix. Once we had a server and could start thinking about, all right, we got a server, what can we do here with our router, and yeah, got the data loading. Then we figured out, oh, shoot, we can do mutations as well with forms, just like old-school web. I missed the part. I did a putsch of PHP in Rails and stuff too. I kind of skipped the server side of stuff in my career but did a lot of that too. Then we brought it all over to React Router. Which is why we're in episode today. We did all this data stuff. Can this fit in React Router just standalone? And we discovered, yeah, it totally can. And it worked out.

JASON: Yeah, so this is actually -- I'm really curious about this because it is a -- when I read about this, like you announced 6.4, and you kind of teased at things like mutations and data loaders. Then you hit me up and said let's do an episode. So I said, okay, I'm not going to read anything. (Laughter) so I've done no research. So all I have is questions. One of the things that kind of struck me was when you start talking about data loaders and things, my experience with React Router has always been that it is a -- you know, it's this very specific routing paradigm where I'm kind of setting up a route provider. Then this route serves me this component. That's the way I've always kind of thought about routing. And I have my guards. Like I can check whether or not somebody is logged in and bounce them to another route or something. But when I started thinking about the data loading and the mutations and some of the other pieces you were talking about, in my head I was like, does that mean -- like, did the whole paradigm change? So I guess as you're thinking about these, what, to you -- like, how does it fit into the mental model of somebody like me who remembers, you know, React Router 4 and is now coming to the 6.4?

RYAN: Yeah, that is a great question. Because it was basically here's the URL, match it to a hierarchy of components, and render them.

JASON: Right.

RYAN: Click a link, change the URL, change the components. Then data and everything else, that was like -- that's irrelevant. That's all just side effects of stuff, right. If you go back one version to V3 in React Router, we had a hook called on enter. If you go to V1, it was called on transition to or will transition to or something like that. But the first versions of React Router actually had this concept in them.

JASON: Interesting.

RYAN: On enter, I want to do something, and let's wait for that enter hook to finish before we move on. Ember's router has this too. I haven't used Ember in a very long time, but the Ember I used had a model hook and did the same similar thing. But if you go back, like forget client-side routing. This is a question we get all the time. Criticism sometimes. I don't know why your client-side router has to get involved in data and forms. If you just think about a server router, Rails, Express, your API routes in any framework, there's a router there that handles post requests. A post request is just a form submission. Or sorry, a form submission is just a post request. So if you're building an Express app, you're saying app.get login and you render a login page. Maybe the user is already here and let's redirect them. So if you just take JavaScript out of the equation, forget about everything you know about React Router in the past, and just think about routers on the web in general, it's actually a lot more like that now. When you go to the login page, you want to maybe load a user. If they're there, redirect them somewhere else, or you're going to render the component. They're going to post to -- there's a form, right, they're going to submit it. It's going to go somewhere like create session. And what the browser does there is it will actually serialize the form that the user filled out into a string or a thing called multipart form data for file uploads and stuff. It basically serializes whatever is in the form, that dom state, makes a request. This is all just the browser. Then it sends it off to the server. Then Express or Rails or whatever can pull that off of the request, do something with the database, and then send back a new response or redirect them to another page. So if you want to get the mental model right now with React Router, just think of a server router. It's got all the same pieces as one of those, but when it's in the browser, now it has a whole bunch of stuff for the pending states and everything else. We'll probably get more into that as we get through. But that's the model. You can keep using it the way that it always has worked as well. Like, this isn't -- it's a dot version. It's 6.3 to 6.4.

JASON: So it's not a breaking change.

RYAN: It's not a breaking change. There's just a new mode almost. You can I have of it as V7, but it put it in 6 so you can incrementally migrate if you want to.

JASON: This is something I do think in general the React community does really well, is thinking about not breaking backward compatibility. It's maybe a reaction to a lot of people move to React after the Angular to Angular 2 split.

RYAN: Yeah, (laughter).

JASON: They were like, maybe let's never do that again. But what I have really liked is typically speaking, if I'm using a library, except in rare cases, you can kind of just never update your code again but continue to update the dependencies and you're probably going to be fine. At least with the major libraries.

RYAN: With the runtime stuff. The world of bundlers in the React ecosystem, those things, you fire those and they sometimes explode in your face. If you can get your bundler to compile what you want to do, in general, the React world does a great job. It's hard.

JASON: It is, yeah.

RYAN: Way harder to build a library that way. But I think it's ultimately worth it. Especially with the -- like React Router has so many users. We did a big breaking change from V3 to V4. But I don't think we can -- I just don't want to do that to people with how many people use it at this point.

JASON: Yeah, and it also creates that, like, that tectonic shift. I feel like when Webpack did its last major breaking change, that was kind of like a lot of people took that as the opportunity. I've got to migrate anyway, I might as well migrate to Vite or esbuild or whatever the thing is. So that backward compatibility is also such a good way to keep people, you know, on the tool. If it's not broke, don't fix it, right.

RYAN: We even shipped a backwards compatibility package for v4 to v5 now. Sorry, v5 to v6.

JASON: Nice.

RYAN: Theoretically, you can bring in that backwards compat layer. You basically get to run both APIs together. And I know some people are using it, and it mostly works pretty well. So even when we had our big breaking change, we went back and kind of back filled. Like, okay, we want to help people get from v5 do v6. Then we want to help them get from v6.3 to 6.4 with this data stuff. Ultimately, I'm totally open about it. We want you on Remix. I think it provides a better UX and better DX all around. So we're trying to make that path from v5 to 6 to 6.4 to Remix as smooth as possible. And of course, you can bail at any point, too, and be like, no, this is great, this is where I'm going to stop.

JASON: Okay. So maybe the spiciest question that I'll ask, is there an intention to just roll React Router into Remix entirely? Like, is that the long-term play?

RYAN: Well, we just did it the other way, right. So, no.

JASON: You just took the stuff out of Remix and put it into React Router.

RYAN: Yeah, it went the other way.

JASON: Then React Router is kind of like the guts of Remix urn the hood, right?

RYAN: Yeah, yeah. Remix, we're doing the work right now to put it on router 6.4. Basically t will be the bundler, code splitting, and a server implementation. Your React Router app needs to talk to a server. So Remix is that server. So it'll automatically server render. I mean, if we wanted to drive better adoption, the thing we run into is people are like, oh, Remix is so new. I don't know if we can use it. If we would have just named it React Router SSR, like we could have done that. Then people probably wouldn't have thought, oh, it's brand new. No, no, this is just how you can -- this is a new feature of React Router. It's a server implementation. That's essentially what it is at this point.

JASON: So I'll make an analogy here. Tell me if I'm off base. This is like Remix is the Svelte kit to React Router Svelte. This is the client-side version. Here's the full-stack version.

RYAN: Yeah, yeah. You've got a lot of companies like GitHub. GitHub uses React, but they also use Rails. They server render with Rails. And we want to provide value for organizations like that too. So it's like you can come whole hog with us with Remix and this is our opinion on a web app from soup to nuts, right. But we also have these composable parts. You can use the React Router part and call out to Ruby and put it in the client. Yeah, it's all these pieces where you can adopt as much of it or as little of it as you want.

JASON: Gotcha. There's a phrase I really like. The progressive disclosure of complexity. When you start talking about tools and things --

RYAN: Progressive disclosure of complexity.

JASON: So the meaning of that would be like when you look at a tool like Remix, for example, you have kind of built the abstraction. You're saying this is how to build. This is where your pages go. This is how to handle a form submission. You've abstracted away all this stuff. Then if you want to do something fancy, if you want to eject, right, then you can opt into more complexity by saying, all right, I'm going to use React Router as my routing layer. Then I'm going to build my own back end. We have Rails. We have whatever. But you don't have to opt out of the whole benefit to go -- to build something more complex for your whatever edge case you've got.

RYAN: Yeah, my team doesn't want to or isn't allowed to run a server, right. I'm on a front-end team. We don't get to have servers. We just got to throw static files on a CDN somewhere and talk to the API that some other team built. So you can get most of the benefits of Remix in that scenario with just React Router.

JASON: What I like about it is, like you said, it creates an on-ramp or off-ramp depending on which way you're trying to go. We go build the proof of concept with Remix, and everybody really likes it. IT is like, we can't ship that.

RYAN: We can't run Node here. We're not allowed to. Whatever.

JASON: But you can pull all the benefits you like, most of the benefits you liked out of Remix and use the React Router implementation and then ship that on top of whatever the approved back-end stack is.

RYAN: You're either implementing your own SSR or you're going to skip SSR. Which is totally fine. Lots of internal tools. Who cares.

JASON: Yeah, of course.

RYAN: Then you have to do a tiny little bit of glue to talk to your back-end API, and you're going to have to ship that to the browser. But that's it. It's generally the same thing as Remix at that point. You just have to glue in a couple pieces yourself.

JASON: Nice, okay. I get it. Yeah, I think that connects some dots for me because when I was initially thinking about like when you said you'd pulled things out of Remix to React Router, the mental model wasn't working for me. I don't get how a router and framework are the same thing. But if we look at what all of this software is doing, what Next, Svelte, they're providing the ability to define routes, handle data, put stuff on the screen. That's where we're at. So each one of these things handles differently in the chain. If I want to build a full SPA in vanilla React and that's all I'm using, I can do that. But I find myself building a lot of that tooling, a lot of that glue that holds the app together. If you opt into React Router, you're building less of the glue. You don't have to build your own routing solution. Now you don't have to build your own data loading. Then you go further to Remix and Next, whatever. Now you've got well, we'll do the SSR for you as well. And we do the bundling. We can run this on a serverless function or an edge function or a Node server.

RYAN: Yeah, that's another big piece of Remix. It's an adapter for deploying your server.

JASON: Yeah, and so it kind of -- you get to -- you can lead yourself down the path a little ways. You can make your life as hard as you want. You can go write the site in byte code if you want. Probably shouldn't. But you can choose the layer of abstraction you're comfortable with and opt into more as time goes on.

RYAN: Yeah, like I'm using Supabase. Do I want to have a back end for my front end that talks to Supabase and gets all those benefits? Or would I rather not manage my own server, and do I just want to talk to Supabase straight from the browser? If you're in the first group, you would use Remix and it would be the back end for your front end. Or you can have your own Postgres database. If you're in the other group that doesn't want to run a server at all, just do this stuff from the browser, then you'd use React Router. But in general, you get the same feature development experience.

JASON: Yeah. Okay. So as you're looking at these tools, we talked about a couple like why a team might make a call one way or the other. Do you find there's a sweet spot here where you're saying this is when I would reach for React Router versus Remix or vice versa? In your mind, does it all kind of -- you know, you should be at Remix, but you can go anywhere you want?

RYAN: Uh, I think you get the best user experience with Remix. Remix has this unique ability to move lots of code over to the server. And there's lots of conversations about ways to do that. And I think that Remix does it in a really unique way, as we'll see loaders and actions. With React Router today, it all stays in the browser bundles. But in Remix, all of that code moves into the server. Even stuff like sorting an array. I know that's not a lot of code, but that code doesn't have to happen --

JASON: It's not nothing, though.

RYAN: Yeah, and multiplied over -- like every GraphQL thing you see has this flatten function. Or parsing markdown. Sometimes we ship that to the browser. Date libraries. Some constraints here, but if you know the server time offset and the user's preference, you can move the entire date libraries that you're using, stick all of that in a Remix loader and action, and none of that code goes to the browser. Your whole data abstractions, like I'm talk about to the Shopify API or talking to Supabase, you're going to build abstractions to talk to these things both for data loading and mutations. Like form on submit, I'm going to go talk to my data layer. Because Remix does both data loading and data mutation, all that code can stay on the server. Where if your framework does only data loading and not mutations, all the mutation code has to go here. So on the server, you've got to connect to Supabase. Then in the browser, you also have to connect to Supabase. Then you're going to try to figure out how do I share code between this that works in both places. Remix has this unique ability where that never goes to the client.

JASON: You know, that's actually something that threw me when I first used Next.js, for example. I hit this weird edge case where I had something that was running in get static props, and I had something else that was running like in the use effect. It was the same code. So I had this little utility that I'd written. It would work, and then I moved it to a utility folder, and it wouldn't work. Because the framework had this magic for server parsing and browser parsing, and it would sort of do these things. But because I didn't know that, because I didn't know the way that Webpack was configured and the magic parsing of the files, I managed to break working code not based on the code that was written but by what file it was placed in.

RYAN: Yeah.

JASON: And it wasn't like -- this wasn't like API code. This was like a little helper that included a node module that suddenly explodes if it's in this file versus this file.

RYAN: Yeah, when your code is running in different environments, either to generate static pages, to run a request response cycle, or strictly in the browser, those are three very different environments. Whatever API keys and utility functions you've got. You have to figure out how do I get those in all three places. I'm not going to pretend like you can escape that.

JASON: The way that gets escaped in these frameworks is by -- like, you have a dot-server, a dot-client to isolate it to one environment as a file suffix. Is that right?

RYAN: It's probably out of scope for this.

JASON: Sure, okay. We don't have to talk about it.

RYAN: Remix can do that if you want to force it, but we rely on tree shaking.

JASON: Ah, okay.

RYAN: So we basically just say give me a route module, except the loader and action, and that becomes the browser version of it. And then tree shaking will automatically be like, oh, you brought in this dependency. It's not used in the component. It was only in the loader, so it gets tree shaken out. Next, there's benefits to the way they do it that we don't have. But they magically find are you using it in which thing and pull it out with Babble. But ours is simply export these things, and if you used that in your component, sorry, it's going to blow up.

JASON: Gotcha.

RYAN: Yeah, we could talk for 20 minutes about how all that works.

JASON: I'll stop asking about the deep internals here.

RYAN: A way to make this decision, should I use React Router or Remix and how would a teem make that decision, I think the best UX can come from Remix. In a lot of ways, the best DX. But there's a lot more deployment complexity to shipping a Remix app than a React Router app with static files. If you don't control your back end or you don't want to control your back end, you probably just want to do React Router. If you do have control of your back end, you can just skip the Rails API and all that stuff too. In Remix, you can talk straight to your database. You can talk straight to your GraphQL SDK. You don't have to make a fetch. And I think that leads to the best user experience. But it's at the cost of deployment complexity.

JASON: Yes. I think that's -- yeah, that's maybe a good gradient to draw it on. You know, what do you control as a team, what do you know how to build also. If you've never done any back-end deployment, that can be a steep hill to climb.

RYAN: Sometimes the Remix version is only going to get you half a second faster. And users aren't going to care about that. So yeah, not a big deal.

JASON: So you know, use your judgment. If you've never done it before, consult this very -- like, we have an amazing community. Looking at the chat right now, we've got -- I think that's Nate from Astro. We have Ryan from Solid. We have a great community out here that is very, very helpful. One of the things I really loved about especially lately the web community is just how collaborative it is. It feels like all the frameworks are talking to each other, sharing ideas, actually trading ideas back and forth, and we're seeing all of them actually improve as a result, which is really wonderful.

RYAN: Yeah, it's a pretty healthy frenemy relationship.

JASON: (Laughter)

RYAN: Some of my best friends today were on my rival soccer teams growing up. Soccer is an interesting thing too because it's a lot like web development where sometimes we're competing and other times we're collaborating. You get the teams that like play against each other in their club league, but then they're on the same team in the World Cup and vice versa. So it's that kind of relationship. Sometimes we're in a match against each other. Then other times, we're in a match together on our country's team.

JASON: And I think that actually points to something really good, which is that competition is healthy. But there's a difference between competition and antipathy. You know? Trying to take each other down doesn't do anybody any good because eventually you're going to have to work together. This industry is too small to think that you can just be --

RYAN: You're going to end up working for somebody who you blocked on Twitter if that's how you're rolling in life.

JASON: But you know, if you are competitive, that's great because it pushes everybody forward. So it's a tricky line to walk, but you know, to --

RYAN: I get stuck when people keep poking me. Why don't you do SSG in Remix? I don't want to talk to you about it. Hey, why don't you do SSG? Okay, here's what I think about SSG. What the heck? You're a jerk! I will not ever talk about SSG again in my life.

JASON: (Laughter) I mean, that's the adventure, right?

RYAN: But yeah, I love seeing everything that everybody is doing in all the other frameworks. Honestly, I don't look at it a whole lot. I just mostly hear about it from other people. I'm not cruising the docs for Astro or anything. I mostly look at what tech we've got, and I'm like, what do I want this to do? Then we make it do that. That's generally how we roll.

JASON: Yeah, yeah. For sure. But yeah, so I'm seeing a couple reports of echo in the audio, which I think --

RYAN: That's my fault, probably.

JASON: Maybe if you want to tap your audio down a little bit so I'm not doing the round trip.

RYAN: Yeah, I got you. And sorry once again, everybody. I'm in the middle of moving. I thought I had headphones here, but I don't. Actually, I do. I have some without pads.

JASON: Oh, no. That's going to be comfortable. So while you're setting up headphones, I'm actually going to switch us over. Now I want to start playing with this and make sure we have plenty of time. I'm going to put us into the pair programming mode. Let's just do the first round of reminding everyone we have live captioning on this episode and every episode. That is made possible through the support of our sponsors -- sorry. Hold on. First of all, Rachel is here today. Thank you, Rachel, for doing the captioning. That's provided by White Coat Captioning. The sponsors make this possible. Netlify, Nx, New Relic, and coming soon, Pluralsight, are all kicking in to make the show more accessible to more people, which I appreciate very much. We're talking to Ryan, who's been on the show before. You can find this episode on Remix, if you want to dig way more into Remix code. Then you can also find Ryan on Twitter @ryanflorence.

RYAN: Okay. I have my crappy headphones.

JASON: I have this fun bug that is happening where Twitter has decided I'm on mobile, and it won't let me not be on mobile. So I just have to load mobile Twitter now. So anyway. All right. I am going to go to the React Router docs. Then I'm going to let you take over. What should I do next? Can you hear me?

RYAN: Yeah, I can hear you. Sorry. I was looking at the chat, which is why I don't usually have it up. Then I don't listen to you.

JASON: No worries. Yeah, so I am now looking at the new React Router docs. I'm ready to rock.

RYAN: Yeah, let's do this. I'm new, right? Are you new? Why don't you read those headings. Which one is you?

JASON: What's new in 6.4. I'm new. I'm on v5. Okay. I'm new to that. I'll click that button. Hey, I know that guy. So here's the tutorial. We'll be building a small but feature-rich app that lets you keep track of your contacts. All right. That's also a really good use of, you know, it's small and not brutal to build.

RYAN: There's so many little details. Like the little spinner. When you submit the form, now what?

JASON: Got it.

RYAN: How much time do we have?

JASON: We have about an hour. Call it 50 minutes.

RYAN: I have this terrible habit of doing workshops where I go on and on about the details of all the things that are happening. So I'm going to try to keep that to a minimum today.

JASON: That is my kryptonite too. I love the rabbit holes.

RYAN: Talking about getting servers out of client-side bundles.

JASON: I know. (Laughter)

RYAN: Two guys, two beards, two hoodies, and lots of tangents.

JASON: Infinite tangents. Okay. So I'm going to -- wait. I meant to make that directory. Then I'm going to go into it. And I'm going to run this code, put it in here. And you wanted to use a template. So I'll grab this.

RYAN: Vite Conf is happening right now?

JASON: Literally happening right now. It might be possible to watch me both on this stream and introducing the edge section of Vite Conf, which I'm doing through quantum magic.

RYAN: I guess it's three guys, three beards, and three hats.

JASON: (Laughter). I am indeed wearing a different hat. So that's, yeah. I think I'm wearing a different hat at least. All right. So I have now a basic site. I'm going to open this up so we can take a peek at it. Pretty straightforward. Wait, let me do -- my git ignore catches this. I'm going to init and open it. So won't gray everything out. So we have our app CSS and app JSX.

RYAN: Yeah, ignore. We tell you to delete all that.

JASON: Standard Vite app. We haven't done anything with React Router yet. So now I need to install. We have the React Router dom. Local forage. Now, this is for data?

RYAN: Yeah, so all the new APIs are all about basically CRUD, like I was saying before. Form submissions have always been a part of routers on the web forever. That's what all these features are about. So instead of having some API that might go down or whatever for this tutorial, we just -- local forage is a nice little wrapper around browser storage. And it's actually going to store stuff in index DB. So that way we don't need a server or something to exercise the APIs.

JASON: Nice. Then I'm assuming these are utilities so we don't have to write our own arrays?

RYAN: Yeah, at the end of the tutorial, we do like a search feature.

JASON: Got it.

RYAN: But guess what, that's also a form submission.

JASON: Yes. Okay. So we have those going. My internet is behaving well today. So that's good. Let's get this thing running. And open this up in our browser.

RYAN: Wonder how much time they spent thinking about 5173. Is there some secret back there?

JASON: I'm always curious how people choose their ports. How did we land on 8888 as the default?

RYAN: I like 3000 because of Andre 3000, personally.

JASON: That's a good one. Okay. So I now have my app up. It's a default Vite app. And we've got some pre-written CSS. Okay, fine. The chat is already making fun of me because I love writing CSS. I spend way too much time on it.

RYAN: Don't do it. Just copy this.

JASON: You want me to put this in index?

RYAN: Just copy and paste it into the index CSS.

JASON: Okay. And that'll give us --

RYAN: Don't worry about it yet.

JASON: Some basics. Let me get back over here. We're creating, reading, searching. So we have some data model. Okay. So this is stuff that we don't really care about for --

RYAN: Oh, we want that one. Just go paste it. Pretend like you have a back-end team that built an API for you.

JASON: So we're going to put this in source contacts. Okay. So I'm just going to trust the process here and not even read that code.

RYAN: That's the point.

JASON: Yeah, like, we got a good team. We don't have to talk to them. So all we need in the source folder is contacts, main, and index.css. So we can get rid of everything else. Get out of here. Get out of here. Get out of here. All right. Then I'll just change this into a little buddy so it doesn't explode.

RYAN: Then get rid of that import on line three. There we go.

JASON: So now error free. Back end in place. CSS is teed up for us.

RYAN: Sometimes it takes a whole weekend of learning something new to get to that point.

JASON: For real. I remember -- so, okay. Ten-second tangent. I remember when I went to IBM. One of the things that was killer was we had all these ideas for ways that we wanted to innovate, but you would burn -- if you got like 10% time, you would burn the whole 10% trying to scaffold the project and never actually got to experiment. So you had to have the willingness to, like, keep an idea across multiple quarters just to get the boilerplate up and running so you could try the thing out. That's one of the things I feel like is such a game change we are Vite, Remix, all these tools coming out that are like run a command and --

RYAN: What did you call it, progressive disclosure?

JASON: Progressive disclosure and just a really, really good defaults. Somebody said it really well on, I think, the stream. They said make the right things easy and the hard things hard.

RYAN: Yeah, yeah. That's something we live by at Remix. Like for example, if you do mutations in loaders, your app UI doesn't automatically reload and reflect that. Sometimes people are like, hey, how come this isn't showing these changes? It's like, because the framework expects you to do stuff in an action, not in a loader like that, in order to get it to reload. Well, can't I just call a hook to get it to reload everything? Yeah, hang on, hang on. If you're doing mutations in git and you have same-site lax cookies, you now have a security vulnerability. So we don't have to teach anybody that in Remix. But their app breaks when they do it wrong. Then when they do it right, they're just like, oh, the app works. But they don't know they just closed a security hole as well. So yeah, that's -- make the right thing the easy thing and the wrong thing the hard. I love that phrase.

JASON: Nicki is telling us in the chat the 5173 is Vite in code.

RYAN: I imagined there was some sort of thought there. I love that. I can talk about the Remix loader for 20 minutes, about like all the history and all the music stuff.

JASON: Oh, yeah, yeah. All right. So let's dive in and get a router going. I've already installed a router, React Router dom. So I'm going to -- let me pull my code up. Let me just collapse this on down. Let's build some stuff. So in my main.jsx, I'm going to import a few things from React Router dom. The create browser router. I want router provider. And I want route. Now that I've got those, we have -- okay. So the router lives outside of the app as like its own const. So we have a router.

RYAN: Yep, that's React 18. We didn't think you'd like seeing a bunch of fetches go off and get canceled and go off a second time.

JASON: Mm-hmm. So this is our default element here. Good. And I need to make something -- oh, I closed this. There it is. What are you unhappy about? Oh, it needs to be an array. Got it. All right. My reading comprehension is poor, but we'll get there in the end. Then down here, I'm going to router provider. Router equals router.

RYAN: Hey.

JASON: There's an app. We have done it. Okay. So this is straightforward. And as a -- I don't know why, but this is the way my brain works. So this makes me happy. I like the idea of an object to do you have define things. I do like when you define routes in code and stuff.

RYAN: Yeah, like with the JSX stuff.

JASON: You can make that make sense in JSX, but I like this. This is pleasing to me.

RYAN: And there's some constraints too, why we want it out there so you aren't trying to set state and change your routes. That doesn't work with data loading and everything else. Otherwise, you're going to get -- anyway. You can still use the route JSX stuff if you want. At the bottom of the tutorial, you'll see that. But I don't know. I think we're encouraging this way to do it. I personally still do the JSX stuff inside of there. But whatever. It doesn't matter. Do it one way or the other.

JASON: Okay. All right. So we're going to do source routes and root. So let's get back in here. And I'm going to do routes/root.jsx. Okay. And then this is going to be a default component. And I'm going to go ahead and copy/paste this because it looks like a lot of code, and nobody wants to watch me write HTML.

RYAN: Yep. And it's not really relevant to what we're doing.

JASON: So just at the high level, we're making a side bar. Here's our contacts, a form. And a little nav. Okay. That make sense to me. And we're not using it yet, so nothing has happened. So now we need to actually import that. So let's go back over here. Route from root.

RYAN: I always feel bad we did that to them.

JASON: Oh, look at that CSS kicking in for us.

RYAN: Yeah, you can thank Jim Nielsen for that.

JASON: Thank you, Jim Nielsen, for saving the chat from watching me write CSS for 40 minutes. So that's night and straightforward. That gets us to where we want to go.

RYAN: Now we have a router and we can actually do something with it.

JASON: And this is super understandable. If anybody has written React before, you know we've got a component. The component looks like this. The component itself has no awareness of React Router. It's just a component. And we're saying when you hit this end point, just a straight slash, show this component. Like, this is, I think, why React Router has such a stronghold on the industry. It's very clear. Show this component when this is true. It's very logical. You can explain it to anybody. And I think that makes it -- it just really makes things comprehensible.

RYAN: It also has a name that makes you think it's official.

JASON: That's true. It does sound very official. All right. So let's get --

RYAN: It's all about marketing when it comes to OSS, friends.

JASON: This is fair. Okay. So we broke it. We broke it because I 404ed. So there's a contacts end point here. If I make this a little smaller -- okay. Make this a little bigger so we can see the routes. I want to be able to actually see our thing here. I want to see the URLs. So we're at contacts one. This one here does what we want. This one breaks. This one also breaks. And that is because we're not handling -- oh, look at this contextual error.

RYAN: Hey, developer.

JASON: Look at it go. So you can provide a way better UX by providing an error element on the route. So that would mean --

RYAN: Let's just keep going through the tutorial.

JASON: Okay. We're going to get an actual error page. Okay. Let's create an error page. And that lives in source. So error-page.jsx. And this is going to give us use route error. This is just a hook that's going to source whatever the error is from React Router.

RYAN: So React Router is rendering your routes, loading your data, and handling your form submissions. So any time there's an error you didn't catch yourself, we can catch it, and we'll render an error boundary. Then this hook gives you whatever that that was thrown is.

JASON: Got it. Okay. So this is kind of its own thing. Then we'll come back into the main. We will grab the error page.

RYAN: As you're typing that out, one thing I love to do and have started doing in our tutorials is to always deal with error handling early because not only is it good for users to see what's wrong, but you're going to screw up your code, and you're going to see errors. So it's good to get that done up front because then it's going to speed up the rest of your development.

JASON: Mm-hmm. And look, we're already there. This is significantly better than what we were getting before, where it was kind of just giving us a mess. And now when we click through, it's like hey, you didn't implement that yet.

RYAN: So here's a little interesting nuance. React Router -- too many "re" words. Can you imagine if your name was Remi and you're involved with React and all these things? Anyway. If the thrown thing, if whatever got thrown while you were loading or rendering or whatever is a web-fetch response, kind of sounds weird that React Router uses responses, but it's part of the dom API, that status text is going to be pulled from there. That's why it said not found on our error page. That's just like a server, right. You have your status text. Those ideas are now in React Router. The idea of requests and responses and everything else. Because that's what they represent. But we'll get more into that a little later in the tutorial.

JASON: Got it. Yeah. Nicki, I don't think I understand your question about the capital letters. The error page doesn't have a -- it's still a component. So if I'm misunderstanding your question, can you rephrase it, please?

JASON: And then there's a question from -- I'm so sorry. I'm not going to attempt to say your name. If the browser is defined statically outside of the components, how would you make the forward slash direct to the landing page when logged out and direct to your default view when logged in? I have a feeling we're going to be able to touch on conditional routing throughout the tutorial.

RYAN: Your loader. We'll get to loaders. But we even have a redirect function now too. So your loader will see are they logged in, yes or no. If yes, just keep going. If no, redirect them to some other page. Or it's just data. Do I have a user or not? Then your component can decide what to render.

JASON: Mm-hmm, yeah. Okay, great.

RYAN: But it's all very server feeling now in React Router. You got to think like the back-end team.

JASON: Yeah, for sure. For sure. And I'm not going to get on this tangent because -- (laughter)

RYAN: Yeah, keep going. We haven't even gotten started.

JASON: So we've got the contact route API, route API, and this is going to give us a form. So we have the actual contact. It's going to show an avatar, their name, favorite contact. Then we have Twitter, notes about them, and then a form to edit or delete with a confirm built in. Great, cool. And the favorite is the star. All right. Cool. So I'm going to grab all this.

RYAN: We should add a copy button on those things.

JASON: Get a contact.jsx. Nothing up my sleeve. And now we have a contact thing. And then I'm assuming the next step is going to be actually having this render. Yep. So we go back to here.

RYAN: So this is the kind of stuff with Remix where you don't have to wire up. You don't have to wire these files to your routes. It just happens.

JASON: Right, right, right. So then we get in here. We say the path is going to be contacts. Then this -- for anybody who's never seen this before -- you want to talk through that?

RYAN: We call it a dynamic segment. So it's just placeholder. So different values, like their ID, can go in the URL and it'll match. We'll parse it out and pass it to different APIs, like loaders and use params and stuff like that.

JASON: Awesome. So that way when we do the contact/1, contact/2, this just gets caught. Then are we already using that? We're not yet. Okay. So we'll get our fallback component here.

RYAN: Yeah, here you go. That's it.

JASON: These are like the fallback values.

RYAN: Oh, got you, yeah.

JASON: So we're in a contact now. We have an actual contact. But it's not pulling based on who it is. It's just the -- right?

RYAN: Yeah.

JASON: So next up, oh, nested. Let's do nested routes. Obviously that makes sense.

RYAN: Yeah, I want to keep that side bar up while I'm navigating around. We'll see how you do that with React Router.

JASON: Okay. Cool. Okay. This makes sense. So we go in, and instead of making it its own route, we're going to take this piece, put it inside here.

RYAN: Yep, and this is an array, just in case you forgot.

JASON: Got it. Okay. So now, did I miss a step?

RYAN: Go back. Do you have a slash on there?

JASON: I do not have a slash on there.

RYAN: Get rid of that slash on the path. It should work, but maybe we have a bug on line 16.

JASON: Oh, here. Got it.

RYAN: That shouldn't matter. Oh, never mind. Read the next part of the tutorial.

JASON: A blank page to the right. Look at us go. (Laughter). We forgot to add an outlet. Good. Good.

RYAN: Should have warned you.

JASON: I'm going to import the outlet from React Router dom. Then we're going to put that in the detail, right? Yes. So this --

RYAN: Boom.

JASON: Yeah, this is a cool thing. I've seen slots. React children has always kind of been one of these. What I like about, like, outlets and stuff like that, is that it feels -- like something about it in my brain just is comfortable with that idea of, like, oh, I've written something and this is my placeholder. I know how variables work. This is my variable for, like, whatever the sub-route is.

RYAN: Yeah, React Router figured out what the heck is supposed to go in the outlet. Then you get to decide where it goes. But you don't have to worry about what it is.

JASON: Exactly. And I feel like I've done a lot of work on like trying to catch the render partially and stop this sub-tree from rendering so I can stick this thing in there. It's complexity that usually causes me to give up. You know what, this UI is not that important, let's re-render the whole thing.

RYAN: Well, it doesn't re-render the whole thing. That's why it's cool with React. React is only changing that part of the page.

JASON: That's what I mean. This makes the right thing easy because you just do it, and it just works. You don't have to go in and manually escape up of these render loops and things to make this function. I think that's really, really cool.

RYAN: And no layout code. No repeating, oh, I'm going to put the same route layout around every single one of my pages. You just made the route once. Then we pop something inside of there.

JASON: That's definitely -- yeah, I've struggled a little bit with that. People talk about the provider pyramid. I've ended up with like the wrapper element pyramid where I have my route layout, my sub-layout, my sub-sub-layout.

RYAN: Yeah, React Router does that for you. It's in your route config.

JASON: Yeah, it's nice. All right. Let's do some client-side routing. So we're going to now add the link component, which comes in the same place as outlet. Then we're just going to, I assume, update these to be links.

RYAN: Yeah, change them to link to. So what's interesting is we've been clicking those links, but we weren't actually using client-side routing. That was going to the Vite server. Then Vite was saying let's run this whole page again. So we weren't using client-side routing yet. We were making full document requests.

JASON: And something that is important to note about the Vite server is it's smart enough to do the catch-all route and redirect to the route. Because otherwise what'll happen is you'll build this and think, this is working great, and then you'll deploy and it'll all break because you aren't actually re-directing all of your traffic back to the route page. But it's very nice that Vite does that for you.

RYAN: Yeah.

JASON: So now if I refresh the page, we are no longer doing any reloads. We're just getting the --

RYAN: Yeah, we're doing the client-side routing.

JASON: Okay. So that'll make more sense when we put real data in here. But it's still nice to see. So let's load some actual data.

RYAN: Yeah, this is where 6.4 comes in.

JASON: Okay. So contacts, ID.

RYAN: So each segment of the URL almost always corresponds to data.


RYAN: And the data you need almost always corresponds to a layout. So like 90%, 95%, 99% -- I haven't run the statistics or collected the data -- but the vast majority of the time when you have data in an app, the URL told you what it was, and some portion of that URL is coupled to some layout. So this is the really big convention of React Router in data loading. We can say, all right, we've got our nested routes that are URL segments. So far we have slash and contacts ID. Those are two nested layouts. Now we can couple data at each one of those levels and let React Router deal with it.

JASON: Did I do this wrong? Contact. I think I typoed that file name. Okay. No, I didn't. I need to go up a level. That's what's wrong.

RYAN: Somebody asked can you handle focus with the outlet. This is a long conversation. But you have to know data. That's why we haven't had this stuff in React Router before. But we are scheduling the work soon to give you hooks to manage focus on route changes, which should be pretty awesome. But we don't have it yet.

JASON: So I'm seeing a loader, which is a thing I first saw in Remix. I had assumed that you had to do some kind of magic parsing with loaders to make them function. So how is this different?

RYAN: This is all just going to the browser. This is what makes Remix unique from React Router. Remix can pull these out from the client bundles and leave all that code up on the server.

JASON: Got it.

RYAN: With React Router --

JASON: Importing it directly.

RYAN: Yeah, yeah.

JASON: Got it. I understand. So back to here. Then we have this. Then we get loader as route loader. Then down here, we're going to configure the loader.

RYAN: Now we say, all right, this route has a path, it has an element, has an error element, then it has some data. Then we just couple them all, and React Router will do the rest.

JASON: And then to get it, we've got a hook. All right. Hooks make sense to me. I can use those. We're going to go over here.

RYAN: Hooks don't make sense to everybody. I'm just kidding. Keep going.

JASON: So we have our contacts. Then it should be an equals, not a from. And I'm going to use the loader data.

RYAN: Before anyone asks, no, we haven't made the types great there yet. It just returns an unknown. We will get there.

JASON: So then in our nav, we're going to loop. So contacts, map, get out a contact. And that is going to return some element. So we know that -- oh, I need to check first if we have any. So contacts length. We'll move this piece up, move this over. This is going to go in here. And I need to group it down here.

RYAN: If this doesn't work, we'll just go copy/paste it.

JASON: Yes, exactly. Then what we've got in here is a key of our contact. We're going to link to the contact and their actual ID.

RYAN: Just so you know, we started this on Remix before Svelte kit existed.

JASON: So first contact, last. Then we can just drop in our contact first.

RYAN: They both smell great. It's like hamburgers and tacos. You can't go wrong.

JASON: That is a fact. I saw on the internet an absolutely cursed food item yesterday.

RYAN: Yeah, pickle cheesecake.

JASON: Pickle cheesecake. Who? Who does this?

RYAN: Your burger has a pickle and cheese on it.

JASON: I know. It's like it should be right, but it's very much not right. Umm, I'm doing this. Then I'm just going to drop in the rest of these.

RYAN: I think when people see React Router has a form component, they think it's a pickle cheesecake. So, maybe it is. Maybe it's delicious. I'm bailing. Oh, there's another condition that I didn't grab. Okay. We're just bailing. Copy/pasting. Now we have no contacts.

RYAN: Yep.

JASON: Which makes sense because we haven't added any yet because I believe if I saw that code was looking at local storage, we haven't created anything, so why would there be anything there. Okay. So no contacts. Next, we want to -- we won't create our first contact. Let's talk about HTML.

RYAN: We can skip that part.

JASON: Okay. We're skipping.

RYAN: The point is browsers know how to serialize a form. We kind of talked about it before. Then make a request.

JASON: Yeah, yeah.

RYAN: I guess I can cover it real quick. So a client-side router, what does it do? You click a link. It prevents the browser from doing its default thing and making requests to the server. Same thing with forms now in React Router. When you submit a form, we prevent the default thing. Then we create a request and send it to an action. We basically completely emulate what the browser is doing. The reason to do this is because if you just rely on browser behavior, HTTP, HTML on a server, the best UX you can get, which is a good one, is a spinning favicon. That's it. You submit a form, spinning favicon, next page. So the whole point of bringing in an SPA router like this is to create a more interesting experience than that. So that's what that section was talking about.

JASON: All right. So I'm going to -- I've imported my form. I just changed this from being a lower case F form to being a Remix Router form. Now I'm exporting an action. My action is going to await create contact.

RYAN: It's just going to make a blank one right now. We're just learning the plumbing between a form and an action.

JASON: Got it. Then we come back here. We're going to get our -- let me click. There we go. We're going to get our action as root action. So we can come down here and actually use it. And now we can click. So here is my new.

RYAN: Boom.

JASON: And it gave us a blanky.

RYAN: Yeah, click it again. Okay. So what's happening is it's serializing our form, which has nothing in it. Then sending it over to the action, calling the action, and then calling the loaders again to get the data and then re-rendering the page. Like, look at the code you just wrote. You made an action and wrote a form.

JASON: Right.

RYAN: You didn't tell it to revalidate all the data. You didn't grab some context object and say, hey, here's my new user or go up to the list component and be like, hey, here they are. There's no step three, right. It's step one, make a form. Step two, the business logic to create the thing, the app-specific stuff. Then React Router connects it all together, submits it, calls all the functions, and revalidates everything for you. You're not going to see it down there because it's all local storage.

JASON: Oh, it's all local storage. Right, because we're doing it on this side with the actions, it's all client side.

RYAN: Yeah, everything is in the client.

JASON: And because it's local storage, there's no requests because we use JavaScript to write directly. That makes sense.

RYAN: All right.

JASON: Cool. All right. Onward. Now we have the ability to create infinite blank contacts. I have all sorts of friends and no information about them. So creates an empty contact. It looks like this is what you just told me. So URL params in loaders. We want to click on the no-name record. And it gives us our whacky ID. Good. Real ID for the record. And reviewing that, that make sense. Okay. So the next thing we want to do is we're going to -- ah, here we go. Now we're going to use this data. So in our contact, we're going to include use loader data. Then down here, we're going to import contact from contacts. Thank you, computer. Then we can do our loader. I'm going to copy/paste this because we've already done it before. With one exception here. So this is the piece that gets called. Because we -- oh, we haven't registered it yet.

RYAN: Down from on line 21. We have that dynamic segment, contact ID. In the URL, we have that slug for them. So when the loader gets called, React Router parses that out and passes it to you on params, dot, and whatever you named it. Contact ID.

JASON: Yeah, so it gives us params, then params contact ID. That makes sense. Then we're going to use the loader data in here. And get rid of our dummy contact. So now we've got our loader data. Nothing else changes because this was all built against the same data model.

RYAN: Yeah.

JASON: So it doesn't like what I've done. Here?

RYAN: We haven't hooked up the loader to the route.

JASON: Oh, you're right. You're right. So we need to import the loader as contact loader over in main.jsx.

RYAN: Isn't that cool, though? It caught our render error and put in a custom element. That's what your user will see. They'll see that kind of a message.

JASON: Okay. So now we want loader. We'll do a contact loader. Save. And now it works. Aha. All right. Updating data.

RYAN: No use effect. No state you manage yourself. You just said give me the contact.

JASON: Mm-hmm. Okay. So next we want to actually put some data in here, which is good. So let's do this edit route. So routes, edit.jsx.

RYAN: Nothing new here. You can copy/paste it.

JASON: Yeah, let's copy/paste. What I have just done?

RYAN: Whoa.

JASON: I think something is touching my -- hello? Oh, that was weird. It was like I had an extra -- a ghost finger on my touch pad there.

RYAN: I hate that. It's always my palm because these things are so huge now. This part of my hand will like -- I need little tiny hands.

JASON: I need elbow rejection on my track pad, Apple. So we've got our edit here. Then I can bring this in. Okay. So I'll just copy/paste this one in as well. This is going to be the -- whatever the contact ID is, slash edit. That's going to go in as a child route of our main route. And it also uses the contact loader.

RYAN: Yeah, don't take that super seriously. Some people say, am I trying to share loaders? No, I was lazy when I made the tutorial.

JASON: Okay. So that's in here. Then edit contact from routes edit. Good, thank you. So now if I click this, does it just work? We want it to be rendered in the root route's outlet. So we made it a sibling to the existing child route. So if I just click this, it does the thing. It does the thing. Wonderful. Absolutely wonderful.

RYAN: Want to know something interesting? That was a button. Just look at the markup for that button.

JASON: Yeah, I saw that. It was in the contact page.

RYAN: Form action equals edit. So link or anchors, ahref, and form action r actually like the same component. They're the same thing. Action is the same as href. So if you submit a form that doesn't have a post method on it, it does the exact same thing in the browser that a link would do. It just changes the URL. So React Router emulates that as well. We're in a form, we had a button there, and we go there. Would I make this edit link a button? Probably not because they have different keyboard events and things like that. Like, you can't right click and open in a new window. Those kinds of things with a button inside of a form. But maybe you want that. Maybe you don't want them to open it in a different tab that way.

JASON: Yeah.

RYAN: But anyway, I just put that in there as an isn't this interesting. Links and forms are actually -- like, this is why React Router is involved with forms. Because forms, since the dawn of the form element, are navigation API and always have been. They change the URL. So that's why we're involved with this as React Router.

JASON: Yeah, all right. Awesome. Okay. So let's get into redirects. All right. So we're going to update this contact with form data. So we've got this update contact here. And an action that will get the form data. That's nice because this is just built into the -- is this just built into the browser? Or you run form data on it in

RYAN: Well, there's a lot going on here. I'm going to start at the beginning. So without JavaScript, without React Router, when you submit a form, we've already covered this, the browser is going to serialize that form into form data, which is a dom API. It's going to send it off to a server. Maybe you're more familiar with different server-side APIs to represent that form data. But in the browser, that's part of the fetch API. That's on request.form data. You could go to the mdn docs and see request.form data is a browser API. So what React Router is doing as a client-side router that handles form submissions is saying, okay, a form on submit, we prevent default. The question is, like, have you ever asked yourself what am I preventing here? All of us have written form on submit, event prevent default. Most of us haven't sat back and been like, what did I just prevent?

JASON: (Laughter)

RYAN: You prevented the browser from serializing the form, making a request, and sending it to the server. So React Router prevents the event, serializes the form, creates a request, and then sends it to your action instead of to your server. But if it went to a server, the server will have a router. It's going to match it and send that request to the handler. Same with React Router. It's going to match whatever form action it is, send it to your action on the route, and then re-render everything when it's done. So kind of -- you might look at that and be like, what's going on. Why do I have a request here? That's what we prevented. We're going to emulate what the browser would have done and hand it to you.

JASON: Got it. That leads us to getting the form data. We figure out what's changed. Then we update our current contact ID, which we're getting out of the params, with whatever has changed. And then redirect to that contact ID. So that's the whole thing, isn't it?

RYAN: You're done. It's one, step two.

JASON: Oh, I have to do the action. So on to the main.jsx. And we're going to get our action out. And toss this on in here. Fill out this form, and now we can set a thing. Okay. Great. So why don't we just put you in here. And I can go over here.

RYAN: This is where we all get everybody's avatars.

JASON: Truly.

RYAN: If they ever put that into a div background or something, I'm going to be so mad.

JASON: Yeah, absolutely hosed. (Laughter). All right. So there we go. We've got this, and then I can add some notes.

RYAN: Did you notice the side bar? I love this. Go add -- yeah, just go to one of those other no names and throw a name in there and hit save. Show me in the code where you're updating that side bar. I love how you're stuck on mobile forever.

JASON: Yeah, I don't get it. Oh, no. Image link.

RYAN: Nice. Boom, over there on the side.

JASON: There it is.

RYAN: I think this is so cool about Remix and React Router now. So what's happening is React Router knows that a form submission pretty much always means you're going to change data. So if you change data, we probably want to go revalidate it from whatever our data source is. Since we have a convention of loaders that says here's where data comes from, we can just call the loaders again after a form submission. So that's why that side bar automatically updates. We know, just by the semantics of a form submission, that when this form submission is done, when your action completes, we should recall all the data on the page to get it to show up. And so it just makes for an awesome default experience. You just do the thing you got to do. Make a form, write an action that actually changes the data, and you don't do anything else to get the UI to update.

JASON: Yeah, it's extremely cool. And then we redirect. So we want to redirect new records to the edit page. Okay. I kind of want to -- we're looking at like probably another ten minutes tops. So I kind of want to jump ahead to the loading states. Can we do that safely?

RYAN: Mm-hmm.

JASON: Okay. I'm going to skip active link styling because I feel like that one people will be able to figure out. Pending UI. That feels like the thing I'm really interested in.

RYAN: Yeah, so this is why we do single-page applications. Like, this is why. The spinning favicon is great. But the reason we made this JavaScript mess over the last five years is this part.

JASON: Loading spinners.

RYAN: Hopefully you can do better than just a bunch of loading spinners. But that was the goal.

JASON: So we're going to do our navigation. It's going to be use navigation. And in here, we can do something like -- we're on the detail div. Let's go detail div and we'll add a class name. That is going to be --

RYAN: Yeah, question from TheSlyox. When you're navigating around React Router knows which layouts are changing. If we're just switching contacts, it's not going to recall the side bar menu. It's not going to call that loader. It's only going to call the loader for the changing routes. But after a form submission, it just calls all of the loaders because we don't know where you may have read that data from. That's not knowledge React Router has. If you don't want -- if you know that and you're like, oh, I don't want the side bar to update or I don't want this other thing to update, there's an API to bail out of it called should revalidate. You can just return false out of that. It gives you some things to figure out should I revalidate or not. But you can bail on that behavior on a route-by-route basis. All right. So reload. I got a little cache there. Yeah. Now we're fading.

JASON: Nice. I mean, very low pain. It's just so nice to have this little bit. And the only thing that changed, to run through this code, is because we grabbed the navigation, the navigation gives us a current state. If the state is loading, we're adding a loading class, which causes the fade. So the work of the animation stuff is happening in the CSS, which I've been told I'm not allowed to touch. So you know, we're relying on the classes here to do this work for us. This is so dang cool. And it looks like for destroy, we're going to get the same thing. We'll just run another form submission that causes this to happen.

RYAN: Yeah, it's always three steps. Write an action, write a form, and then wire up the route.

JASON: Yeah.

RYAN: Over and over and over.

JASON: And then did you see there was a question about -- where was it? What about dynamic forms, like show field XYZ? So when we're doing that, we're just into logic, right. And as long as you're updating form elements, you're still -- whatever submits is going to be form data. Is that correct?

RYAN: It's going to get a little more interesting. We have this thing called use fetcher, which is a hook that's -- so far, everything we've done are these full single navigation events. They're changing the URL. It's the whole app going somewhere. The whole app is submitting a form. And you can read the data that's being submitted, too, so you can do optimistic UI or just say, hey, we're submitting a new contact named Ryan. You know that stuff as it's happening. And I found that's, like, almost enough. Then we have these fetchers that are their own little mini navigation, but they still talk to the routes. So they aren't changing the URL, but they're just -- and the bottom of the tutorial does it with the favorite button. I don't know if we have enough time to do that, but we could do the favorite button down there.

JASON: Let's see. Cancel button.

RYAN: Yeah, get submissions. That's the search. We don't need to do that. Yada-yada-yada.

JASON: Adding a search spinner.

RYAN: Yeah, keep going. Do use fetch. Yeah, here we go. Scroll up from there.

JASON: Okay.

RYAN: Oh, no. That's the very top.

JASON: What was it, use fetch?

RYAN: Yeah, here we go. Mutations without navigation. So data loading too. So to answer the question, you can wire up some events to an on change of input, and then interact with a fetcher, which would be fetcher.load, to go get some data from one of your routes. Then it manages all the state, manages the whole transition, manages errors. If the errors happen, it bubbles those up. So an error in your loader, as you're trying to get -- let's take an example. You pick your city -- or you pick your state -- I don't know. You pick your state and then it lists a bunch of cities, right. That's kind of what these forms sometimes do. So you can do on change on the state. Then you'd call fetcher load, and that fetcher is going to have some state, talk to the route, and then you use that data to then render the rest of the form. Once that thing is populated, you submit the form. Now we're back to, like, the singleton submission navigation thing. And all of it is routes. Especially if you have a server. That's an API route.

JASON: Right.

RYAN: So you keep that model the whole time.

JASON: Awesome. I mean, okay, so we're roughly at time here. So let's spend -- I have one more question that came in that I thought was a good one. Would there be some star as route magic possible in the future? As soon as I read that, I was like Remix. That's star is route. When you set up remix, you're getting the actions and the loaders and all that automatic configuration.

RYAN: Yeah, I think what I would do is I would make a Vite or Webpack plug in just for a file system convention. Then just have that generate the route config for you. So you never import any of it. You just have a routes folder. Then your entry point gets generated automatically. That's where I would go. But yeah, you could do a route star thing on there. You won't get the element, but you get loader and action and stuff like that. Those conventions are what make Remix, Remix.

JASON: Cool. All right.

RYAN: Someone is asking can you cache loader functions, return values like React query? React query is about caching over the history of a whole session. So they work together really well. And we have a doc all about it if you want to go to our docs website. On the left side, it says -- I can't remember what the title is. It's all the way at the bottom under guides. Data library integration.

JASON: There you go. Yeah. So lots and lots of good stuff here. I'm going to toss in the tutorial because this tutorial is great. Where else should people go if they want to dig in? Are there specific examples you want to look at? Migration guides, anything like that?

RYAN: Man, it was just a big effort to get all this over here. We don't have a whole lot of -- oh, we didn't touch on skeleton UI. Deferred data right there. Maybe the feature overview. We got to give the people what you promised. Scroll all the way to the top of the docs.

JASON: All the way to the top of the docs. Feature overview.

RYAN: Now do a find for skeleton UI. I need to put -- I'm going to put a table of contents on these pages. Here we go. Yeah, so what happens is --

JASON: Oh, cool.

RYAN: In your loader, you can say defer. Then that puts it into suspense mode. Because right now when you navigate, we block the next screen on the loader. We got to wait for all that stuff to come before the next gene shows up. We fade out the page so you know something is happening. Otherwise, the app feels super clunky. But sometimes you want to say, hey, I want to go straight to the next page and show skeletons or I want to wait for one or two things but not the rest and show a skeleton for those. That's where defer comes in. So you say defer, issue comments history. In this case, the issue is important. Scroll up just before the defer. Look how we await the issue. But the comments and history are just the promises that get returned. So you hand promises or values to defer, and those promises, we know that's a promise. We're not going to wait for that. But we'll wait for the issue.

JASON: So don't show me anything until the issue is ready because the issue is critical. But comment, they don't matter. They're useful, but they're not critical for the first view.

RYAN: And we can defer all of it if we wanted to. Then scroll down and we have a plain old suspense with a skeleton UI. That's how you get the skeleton UI. Just put a skeleton in there. Then the await component comes from React Router. You're saying I want to resolve the history of this issue. That's the promise we're going to wait for. Then we have a render callback right there, if you don't want the indirection of another component. Then you can render that thing. So it's going to immediately transition, as soon as the issue is done. Render the skeleton for the issue and render the skeleton for the comments. Unless those made it in time. If the history in the comments made it in the same amount of time we're fetching the issue, then we won't get any skeletons. They'll all show up together.

JASON: But as it is spooky season, we want maximum skeletons. So make sure you add a time-out on all of your fetch calls.

RYAN: But yeah, so you can -- like, this is my best -- sorry, I just hit my mic. This is personally my best experience I've ever had with suspense. At this point, I'm like, yeah, suspense is really cool and exciting. But for me, it needed this defer thing to be able to say here's the data I care about, here's the data I don't, and then a router to orchestrate it for me.

JASON: Yeah, I think that totally makes sense. All right. So I got to cut us there because we're out of time. Make sure you go and check this stuff out because it is extremely cool. This will all be available in the show notes. Make sure you go and follow Ryan on Twitter for all sorts of good information. And this episode, like every episode, has been live captioned. We have had Rachel with us today from White Coat Captioning. Thank you very much, Rachel. And that's made possible through the support of our sponsors. We've got Netlify, Nx, New Relic, and Pluralsight. You can go look at the schedule. We have so much good stuff coming up. We're going to play with Webauthn on the web and a whole bunch of other things, including stuff I haven't had time to get on the site yet. Make sure you go, you know, like the show, follow on Twitch, subscribe on YouTube. Do all the things that tell the algorithms to show this stuff to people. And with that, we're going to go find somebody to raid. Ryan, thank you so much for spending some time with us today. This was awesome.

RYAN: Thank you. I always have a blast here.

JASON: All right, y'all. We'll catch you next time. Take care.

Closed captioning and more are made possible by our sponsors: