Let's Learn Remix!
Remix is a React-based framework focused on web fundamentals and modern UX. In this episode, Ryan Florence will teach us what makes it different and how to get started with it!
Links & Resources
Click to toggle the visibility of the transcript
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). 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 Learn With Jason. Today on the show we've got Ryan Florence. How you doing, Ryan?
RYAN: I'm good. My Wi-Fi is doing a lot worse than I am today.
JASON: All right. So we're all just going to hope as hard as we can that we don't have any Wi-Fi issues today. So it's an honor for me to have you on the show. You've been in the community forever. I've been following your work as long as you've been in the community. For folks who aren't familiar, you want to give us a little background?
JASON: I love the kind of lineage that comes in there. When you start thinking about the way that we got to where we are on the web, there is a logical path you can trace backward to where we still -- if you look at what most single-page apps look like, it is still a div. Then there's a script tag below it. We just abstracted that all away with the tooling.
RYAN: Even your component sometimes is like, here's all my hooks that fetch data. Here's my loading states. I don't really -- like, I just have to do those. Then here's the actual body of the component that I want to render. It's not different than the old PHP stuff, really.
JASON: Yeah, I mean, it feels like we go in circles, and we decide that, like, well, this thing is too complicated. Let's do this thing. Oh, wait, we did that a long time ago. Why did we stop doing that? (Laughter)
JASON: Is there a trendy Serif font right now? Like, it's still Georgia.
RYAN: Lobster? I don't know.
JASON: (Laughter) But no, I think -- I love that. What I think is interesting is it does kind of cast a light on the fact that none of these solutions are right or wrong. They're just better tools for different jobs, right. I think that's always -- you know, when you have a unique set of challenges, you create a tool to solve that unique set of challenges. The reason that it feels like we go in circles is because the landscape shifts. The reason that we didn't keep doing, like, static sites was because it was way too hard to manage HTML in the beginning. You had to copy/paste the HTML between every single page on your site. Then we did PHP because that made that easier to manage. Then we figured out that we could compile, and that got rid of some of the complexity with PHP. Now we have edge servers, so we don't need all of the speed advantages that SSG necessarily gives us. So there's so many interesting things that kind of build on the archaeology of how we get to a certain set of decisions.
RYAN: And you know, people say it's circles. It's a spiral upward. It feels like a circle if you're just looking at it from the top. But if you change your perspective and come down nine degrees, you'll see it's a circle that's actually going up. I also like to talk about the Mona Lisa. The greatest work of art of the time that we know of. But it wasn't the work of a single artist. Yeah, one person actually painted it, but everybody around the Mona Lisa was pushing art forward. Wouldn't have gotten the Mona Lisa without all the other artists.
RYAN: So, you know, I think Next JS is great. I think Gatsby is great. I think they have really cool ideas. As we built Remix a couple times, we peeked into the source of Next.js to see how they solved this problem we're running into. And Remix for us is really just, like, how we want to build websites.
JASON: No, I think -- actually, that's an excellent segue because obviously the goal of today is to talk about Remix. So maybe we can start with some context setting, which is we talked about it's how you want to build websites. Specifically, kind of what is the core value prop of Remix? Why build another framework?
RYAN: That seems good.
JASON: Is that an insult or a compliment?
RYAN: I liked it. I think technically I'm an elder millennial. So I'll take it. What is the value prop of remix? It's really hard because if you talk to Michael, you'll get a different answer than if you talk to me. He and I have been partners for -- we're closing in on a decade, it seems like. And we're both full-stack developers. But on the ends, we have a -- I don't know which way to go on the camera. There we go. Oh, closer. The lag is killing me. On the ends, we're a little bit different. We share a whole bunch of experience and expertise and interest in the middle of it. But on the ends, we're a little different. I think that made a great combination when we built Remix together. And we actually just sat for -- it was like this, actually. I didn't even type anything. Mike built all of it, just like you're going to do today. We just paired for six months, building it together. Like 100% pairing. So it's really the -- so if you ask Michael, it's -- I think the biggest thing we're excited about on that end of the spectrum is deploying to the edge. You know, we do SSG to get really good speed. The main value prop of SSG is -- not the pre-rendering. Like, React takes eight milliseconds to render a big document. That's not expensive. The expensive part is going to your database, fetching those resources, maybe processing it into the markdown, turn it into HTML. There's a few things after you get the data that might be slow. The point is let's do anything that's expensive and stick it at the edge next to the users. So when they come and request that document, it's just, you know, coming from memory on a CDN or from a file system. Then just send it over the network.
RYAN: We built Remix with, like -- distributed web infrastructures got really good. Before, the only way to do this was static files, to distribute your app across the globe. But we've got AWS Lambda at Edge. We have Dino Deploy, which is at the Edge. We have Cloud Flare Workers, which runs code at the edge. And Remix already runs on Cloud Flare Workers. And one of our new users just got it working on AWS Lambda. So you can push your code, push your app, like your actual app code across the globe next to the users. Now, you don't have to sacrifice freshness for speed. Like, yeah. So I think that's kind of on the back end, that's what we're really excited about. In the middle what's really cool is you get to use all web technology on the server and the client. So it's really exciting as a front-end developer. We kind of joke that one of our main goals is to trick front-end developers into being full-stack developers without quite realizing it. Like, if you can write a Gatsby Node file that goes off to Sanity.io and pulls down data and hands that to Gatsby, that's back-end code. You're a full-stack developer if you know how to write that. In Remix, you can actually do that stuff in your app. But it's not different code.
RYAN: It's just when you do it.
JASON: Gotcha. Okay. That's a good way of looking at it. And I think that you mentioned you used to build in PHP. You know, the promise of PHP was that, right. You got to just put whatever you needed in the page with the HTML that was going to get you up to the browser, and you could do some dynamic stuff. As you said, you'd hit this cliff because when you got to the browser, you were just 100% on your own. So I'm really excited to see how this works. And honestly, at this point, maybe the best thing to do is just start actually looking at it. So why don't we switch over into pair programming view here. I will start by just doing a quick shout out. This show, like all shows, is being live captioned. So we've got Rachel here with us today from White Coat Captioning, making this show more accessible. So if you need that, that's on the home page. That's made possible through the support of our sponsors, Netlify, Fauna, Auth0, all kicking in to make the show more accessible to more people. Means a lot to me. And we are talking today about Remix. So if you haven't looked at it yet, here it is on the interwebs. You can check this out. Also, just a quick shout out to -- well, to you, I guess. This is such a cool setup. As you scroll down this page, you get cool things like this. Just fun. It's a fun website that's a good explainer page. And if you don't already follow Ryan, go give him a follow on the old interwebs. And with that, I've reached the extent of my knowledge. I actually -- knowing that we were going to do this stream -- like, we've been talking about this for a while.
RYAN: You've avoided it.
JASON: I bought a license back when you first released it. Then we were going to do a stream. Then we were like, oh, wait, let's wait. We knew you were going to open source it, which is really exciting that it's now open sourced. But I kept waiting. So I've actually never used Remix specifically because I wanted to ask you all the beginner questions. So today is a big day for me. I've been waiting. (Laughter)
RYAN: Well, you'll get the guided tour. Let's just hope we don't have any bugs in our last release that are going to screw us up.
JASON: I'm ready. So if I, as I am, coming into this completely new, written a lot of React, written PHP in the past, what's my first step? Like, what should I do to get up and running with a Remix project?
Oh, so we would need to add code?
RYAN: Oh, who's this?
JASON: I should have warned you. The chat can make all sorts of sound effects. They'll mess with us as we go.
RYAN: Okay. That's going to slow down the show because I'm easily distracted.
JASON: Keep that in mind, chat, if you want to stay focused.
RYAN: It's honestly why I like working from home. It's not because I don't like interruptions. It's that I interrupt everybody. All right. Go click on docs, actually. That probably would be the best thing to do. Right there.
JASON: All right.
RYAN: Then -- yeah, getting started. So we strongly encourage you to do one of the tutorials. I'd probably do that quickstart if I was somebody else. But me and you are going to do something different today. Oh, no. Did my camera do that thing where it stops? It did. All right. Here's my other camera. It's going to look gross. Sorry.
JASON: Camera one, camera two.
RYAN: Yeah, camera two. So if I was somebody else, I'd do the quickstart. You build a funny little markdown blog. It's nice and quick, introduces you to a lot of the different APIs in Remix. Yeah, all little tutorial. You should be done with that thing in, like, I don't know, 15, maybe 30 minutes. That's it. Then after you're done with that, you can go to the jokes app deep dive. This is going to overwhelm you if you've only done front-end stuff. Like build heavy front-end things.
JASON: Oh, yeah. We're setting up a database and everything.
RYAN: Yeah, so get ready to dig in because you're a full-stack developer in Remix. So it sets up an SQ lite database. Could be postgress if you'd rather. You're reading, writing to the database, deploying the thing. I think Kent C. Dodds works with us now. He did a livestream and it took six hours.
JASON: I saw that stream go live. Again, I didn't watch it because I'm like, I'm going in with no knowledge.
RYAN: So buckle up, and get ready to go. You're going to see what it feels like to build the whole app. This is one of those things. That wall that I was talking about. A lot of modern front of of-end development has solidified that wall. We've got the team over here that builds the back-end API. We have the team over here that does the front-end thing. Both teams think the other team is a bunch of geniuses. And they both are. They're both great at what they do. But you don't have to have that wall in Remix. You can if you want. You can build an API and consume it with Remix. But Remix lets you do both things in the same file, as we'll see. So yeah, I guess let's go back and just copy and paste that thing right there. There we go.
JASON: All right. Now, do I need to create a folder first? Or is this going to create one for me?
RYAN: Just do that and follow the prompts. It'll ask you where you want it to go.
JASON: Okay. So we're installing. I like that. Okay. So we're going to call this let's learn Remix.
RYAN: You want to do Netlify since that's where you work?
RYAN: This is your project. You do what you want.
RYAN: Maybe. Maybe. Because now we're not going to get the types that tell us what we're supposed to do.
JASON: Ah, crap. That makes me want to go back and do it as TypeScript.
RYAN: We'll be all right. I don't want to -- I don't know if Sanity's API supports it out of the box. I don't want to be typing all their stuff.
JASON: I don't know enough about it. We'll go without the safety and hopefully that'll give us some speed.
RYAN: All right. So what is the first thing you ever do when you open up a new project?
JASON: I immediately start peeking at the -- like, what do we have going on here?
RYAN: What about the file that says read me?
JASON: Never. Never read me. What's that for? (Laughter)
RYAN: So first tip, if a file says read me --
JASON: (Laughter) Oh, I walked right into that, didn't I?
RYAN: Yeah, I think I'm going to rename it to readme_ plz. Did you look at the read me? No.
JASON: I love it. Okay.
RYAN: So here we go. Let's get this deployed, like in the first minute of this.
JASON: Oh, boy. We hit a Corgi stampede already. People are really out to distract you today.
RYAN: Oh, boy. I'm never coming on this again.
JASON: (Laughter) Okay. So here's our project. I just initialize the the project to give us an empty folder. So then I will -- I've got the Netlify CLI. So I will run Netlify init.
RYAN: What's the read me say? I don't know how solid our integration is yet.
JASON: So, it says install the CLI.
RYAN: You need to log in. All right, cool. We have a PR from someone at Netlify who's decreased the number of steps here. We just haven't released that yet.
JASON: Yeah, so the -- I think that was Matt Kain. The Netlify team is working hard to try to remove any friction. We want it to be like a zero-config deploy. I think we have that mostly working. This will get better with time.
JASON: So I'm going to Netlify init.
RYAN: The reason it's a little more complex is because Remix isn't a bunch of static files. It's actually going to be running in a Lambda. Like, when a URL comes, we actually run a function instead of send a built file. Then this next one on line 27, that's the critical thing where nothing will work.
JASON: So I just realized I have to -- I got to actually get this up on GitHub before I do it. I'm going to use the GitHub CLI.
RYAN: I think we're just going to deploy manually.
JASON: Oh, we're going to deploy manually? Got it, got it.
RYAN: Yeah, we're not there yet with Netlify. I think that PR gets us there.
JASON: Got it. So I'll create and deploy the site manually. Put it on my team here. We're going to call this Let's Learn Remix. So I have created the site.
RYAN: All right. Now go over to the read me, step four.
JASON: All right. So we can Netlify env set AWS Lambda JS Runtime.
RYAN: So a lot of people think the environment variables are just for your build. Those are actually not environment variables at all. Those are build variables that, like, go into your code that ends up on the public -- like, someone goes to your website, they're going to see those secrets and environment variables in your code. Since Remix is running on a server, what we're doing here is telling, I guess, AWS, but Netlify, we're going to run a Node version 14. But in env set thing is not just about setting our node run time. You can put your private stripe secrets there. You can put your Sanity.io token. Anything you need to interact with different APIs that are secrets that you don't want anyone else to know about. Every server, like Netlify, has this. It doesn't go in the bundle. It stays on the server.
JASON: So if you were deploying this on another platform, you might be using serverless. You might be using a server. But the general idea is, like, client-side code gets bundled, or not, and then sent to the browser so you can view source. We all saw that issue with -- there was that government site that had loaded all of the data and then put it in a JSON blob. If you viewed source, you could see people's social security numbers and stuff. That's because somebody wasn't thinking about the client side versus the server side. If you have a serverless or server, no secrets, like none of those environment variables are there unless you explicitly return them.
RYAN: And we're tricking people into becoming back-end developers. This is a question we keep getting over and over. Hey, how do I get an environment variable into my browser bundle? We're like, that's not an environment variable.
JASON: Right, right. So I've set the environment there. Then we've got --
RYAN: So now let's skip development and go to deploy. We just want to -- let's get this thing going. Npm run build.
JASON: Npm run build, good.
RYAN: Hang on. Do you see that? You're livestreaming. You just built your front-end app in 350 milliseconds.
JASON: Yeah, that's pretty slick. What are you using under the hood for that?
RYAN: VS Build.
JASON: Nice. Just so fast.
RYAN: Fully code split. All sorts of amazing things.
JASON: You know what, YOLO. Straight to prod.
RYAN: You know, Netlify is super fast. Probably the fastest -- like, you type the deploy thing in the terminal. I think that's the fastest one of everything. Boom, look at that.
JASON: We just took a Remix site live. That is nice. Who knew, everybody? The read me. Just read.
RYAN: Four steps and you're in production.
JASON: Okay. So we've done that.
RYAN: Let's go click around over there.
JASON: Yeah, let's do it.
RYAN: We have these demos in the app.
JASON: These take us out. Let's go here.
RYAN: Yeah, go to actions. This is cool. What's more useful when it's broken?
JASON: Oh, that's -- I don't know.
JASON: That was very fast.
RYAN: That's actually all happening on the server. So open your network tab. Let's watch what Remix is doing under the hood.
JASON: Okay. Let's put this over here, get this network tab up.
RYAN: Go get rid of the previews or whatever. Click the gear in the top right of your network tab. Then show overview. Yeah, get rid of overview.
JASON: Got it, yep. Okay. Oh, wait. Don't close the whole thing, though. What am I doing? Close the gear. That's what I was trying to do. So here's our network tab. I've got this, and I'm going to say -- what do you think, chat? The seal to my package.
RYAN: Let's do one we don't get right.
JASON: Chris says my heart.
RYAN: Okay. Look at the network. First thing, you don't have a type on there. Right click up on name and let's get a new column in there. Right click on any of those table headers. Do method. Post. So Remix made a post to your server. Click on that post right there. Let's look at the payload. So you sent some form data. My heart. Right? So you've done this in React, where you keep all the state on your form. What did they type? When they hit submit, you do form on submit, event/prevent default, and do a fetch with this information. Remix does all that for you. We can look at the code in a second. But it sent us -- actually, let's go to the code and follow what the code is doing at this point. So open up actions inside of app. Yeah, demos. So we put them all in demos so it's easy to delete.
JASON: Got it, got it.
RYAN: Scroll down to the body of the component. We're going to see an export default right here. The actions demo. So here's the UI. Scroll down. There we go. That's the form we just submitted.
JASON: So here's our form.
RYAN: Form method equals post.
JASON: Got it.
RYAN: Look at our input there. We got a button down on line 67. Now scroll back up in the component. Where is everything else? Where's the state? Where's the form on submit?
JASON: Use action data. Is that --
JASON: But still here. Look at this page.
RYAN: Yeah, you're still there. Drop an answer in there.
JASON: All right. What else you got, chat? The seal to my package of cookies. I agree with that one. That is correct, actually. (Laughter)
RYAN: You guys are just having fun. I'm like, I want to talk about Remix. Check out actions. Doesn't this look strangely familiar? Isn't this what our network tab looked like last time?
JASON: Yeah, looks like it did exactly what we wanted.
JASON: Yeah, okay. So, let's see. We've got our answer ref. We have the use action data, which we'll have to look at what that means.
JASON: But we have some server-side rendering happening here where we can see we do have an action message. So that shows up. That's this here.
JASON: Yeah, that is really slick. Let's see. So that's side bar. Then when we look up here -- okay. So here's the action. This is the part that -- and we don't have to attach that action. We can just export it? Is your internet betraying you?
RYAN: Yeah, my internet just blew up on me. I'm going to try tethering.
JASON: Okay. Everybody give us a second while we navigate some Wi-Fi issues. I can hear you. There you are.
RYAN: I'm back. All right. Tethering from my phone. I don't normally --
JASON: Fingers crossed.
RYAN: Okay. So, we back? Am I good?
JASON: We're back. We're good. So what I was looking at here is we have -- so we've got a form, right. This form came out of Remix. So this is a component built into Remix. Then we're exporting an action, but I don't -- so we don't need to include that in the form. It just knows.
RYAN: Yeah, so there's a little trick we do in Remix. Actually, browsers do this. If you don't put an action attribute on your form -- so here's another thing a lot of newer developers I know do. Form has a method and action. You could put action up there, and that's like a URL link. Browsers will just default to what the current URL is. And in Remix, we'll default to this route. So if you leave off the action, then you can put a form in a route, and it will post to the action in that route.
JASON: Got it. Okay. And so effectively, what we've done then is this isn't for the form. This is for handling a post to this page.
RYAN: This is your API route.
JASON: Got it. Okay. So we're just building it right in. For people who have used serverless functions, for example, if I was writing a serverless function, I would have my form submit -- I would post to that serverless function. Then this is the code I would write in my serverless function to handle the incoming posted data.
RYAN: Exactly. In a typical SPA, there's three pieces of code you have to write.
JASON: That's slick. That's really nice. So, yeah. Oh, and this is nice too. I don't have to actually do this stuff myself anymore. Like, I don't have to write out an SEO component.
RYAN: Yeah, exactly. And we'll merge all the meta from every route. So in React Router and in Remix, we have this nested routing. Multiple routes can be active on the page. Think of them as layouts coupled to the URL. In fact, the first visual on the Remix.run website shows this where you have these nested boxes. Each one of those connects to meta. They'll all get merged together and go into the top of the document.
JASON: Yeah, this is actually a really good visualization to show. So when we look at our page, we've got the whole page. That's this blue bit here. Then the sales dashboard, that's a nested route.
RYAN: Yeah, so each one of those connects to meta. They can all set the title for any other meta.
JASON: Then this is connected to this tab. So you have nested routes. Oh, this is slick. This is really, really nice.
RYAN: So you don't have to write layout code. You don't have to repeat your data loading across every route. You don't have to repeat your layouts all over the place. It's just built in because we know from building lots of websites over the years as a community that most of the time this is how it works out. Chunks of the URL match up to chunks of the UI.
JASON: Yeah, and that's what we're always doing, right. What I've noticed is that what makes this hard is that we usually have to do it also manually. We build these complex layouts, then we want them to be uniquely addressable. Then we actually try to do that and we get ourselves completely buried in, you know, state management of URLs and stuff. We go, you know, this is really hard. Maybe we don't have this part of UI linkable. We throw our hands up and say, it's okay, people will click two buttons to get there. That sucks. That's such a bummer of an experience.
RYAN: I'm going to try to switch back over to Wi-Fi. Sorry.
JASON: No, we're good. Let's see if we got any good questions here while everything is going. Jay is asking questions about form submission client side and getting answers from the chat. Thank you for sharing the use submit hook. Let's see. Very cool. This is very cool. We've got lots of stuff going on. Also, welcome, everybody. I see a lot of folks are here. Let's see, Ryan, I think you're back. Maybe. Nope. Ryan is frozen. Now you're back.
RYAN: All right. Am I back? Sorry. I've been working on getting hard wired in, but it's not happening.
JASON: I know the feeling. I had to switch to all ethernet everything in my house. I had somebody drilling holes to run the bigger cable through.
RYAN: I was just working on it yesterday. I ended up ruining -- so I could hard wire down in the kitchen. I was going to do it there. Last night I was trying to get it so I could hard wire in my office and busted all of it. Just kind of hosed now. Okay. All right. Let's keep going. So yeah, we got that.
JASON: We're ready.
RYAN: Someone had a question about -- what was that?
JASON: They were asking if you wanted to programmatically control the submit. And someone shared the use submit hook to do programmatic things.
RYAN: Yeah, like if you want to submit on Blur or on Type or Change, we have a -- there's a declarative way to submit a form. That's the form component. There's an imperative way to submit a form, and that's our use submit hook. It's the same as in React Router. You have the link component to link somewhere. That's the declarative way. Then you've got use navigate, which is the imperative way. And the dom has this, too. You have the AH ref. That's the declarative way. Then you've got window.location.href equals. That's the imperative way to do it. Whenever we have a declarative API, we have an imperative one if you need to control that.
JASON: Great, yeah. Okay.
RYAN: Do you want to put the right answer in?
JASON: Yeah, let's put the right answer in. I had to cheat, but I know it now. So let's go here.
RYAN: So shift, command, P, or whatever OS you're on.
RYAN: Okay. So check out what happened in the terminal. Or in the network tab. We did a post. Then this time the response wasn't red. We didn't send a 400. So before, we were getting a 400. That was a bad submission. Then look. It did a bunch of gets. If you scroll down, we'll see more of them. There's a bunch of resources.
JASON: I see the three that came in.
RYAN: Those are code split bundles we didn't have yet.
RYAN: Not only will it go and get those modules when we redirect to that place, if that had a loader, it's going to call the loader. That's the data on the page. We haven't talked about that stuff yet. Wept straight to forms. If we had multiple routes on the page -- let's go back to the visual on remix.run. If we had multiple routes that are matching up there, and let's say you're looking at the invoice list and you deleted an invoice, maybe on that red screen, there's a list on the left. Shrink your -- do command minus, minus so we can see more of the thing. We got the invoice list on the left. If you deleted an invoice, Remix will automatically, after an action, refetch any data on the page. So that means that that invoice is going to disappear across the app, no matter where it shows up. You don't need a global state management tool. None of that kind of stuff. Remix knows that a get means read data and a post means write data. So when we get a post from you, from a form, we go and process that post. We know that you probably changed some data, and there's a chance that data shows up elsewhere on the page. So we just say, all right, what's on the page? Let's go reload all of it and synchronize what our UI has with what the server has.
JASON: That's super handy. And that's kind of a strategy that we're seeing showing up in a handful of tools where, you know, they sort of know when an action is going to mutate something. I like that's just done for you. It's the sort of thing like you can track it. We're developers, this is our job. We keep track of how data changes and flows through an application. But some of it just gets so byzantine. This affects so much data. It's in the side bar, recommended reading, whatever. You forget to do that. Then your pages are out of sync and everything is broken and you're confused. So having the software just know to do that for you is such a power-up. I want to worry about solving problems, not about managing the inherent complexity of building.
JASON: I think that's great. I just saw Michael show up in the chat. How you doing? Good to have you here. So it looks like he's going to be running chat question support, which is very helpful.
RYAN: Cool. Yeah.
JASON: Okay, great. So at this point, we kind of know the gist here. And what I like about this is you've taken something that used to work, and then we stopped doing it just the browser way because the hard part was making it feel as nice on the user side when it got to the client. So what I like about this is you're not throwing the baby out with the bath water. You're saying the platform is really good at everything except this client-side thing. So why don't you use the platform for everything except the client-side thing, and Remix makes that seamless. I like that general approach to building for the web. I think it feels right.
RYAN: Yeah, Remix emulated the browser up until like now it's time to do something really cool. Then we give you the hooks, like use action data. We have one called use transition. It gives you all the information about the transition. You can build all the modern UX stuff you're going for, but you get to keep that super simple old-school PHP model.
JASON: Also, just looking at this, the other thing that's nice is if I go back -- let's reload this page here. We get, what, 20 kilobytes. That's good. That's a nice way of kind of interacting.
JASON: So you end up in this -- what you can see here with the progressive loaders. You start with the whole thing as a page loading. Second thing is a page loading. Then we've got a subsection loading. Then a sub-subsection loading. This can be a fine experience, but you know, it's clearly -- like, it would be better if you just got -- like, there's the UI.
JASON: Yeah, you broke up a little bit, but we got the sentence at the end.
RYAN: Oh, my gosh. I'm killing you. I could grab my laptop and go downstairs closer to my router.
JASON: I think it's up to you. You know, we're always -- we can always make that switch, but I think we're --
RYAN: We've got like an hour left?
JASON: 40 minutes.
RYAN: I'm going to do it. You poke around the demos. Look around the demos in your app and click around them locally. Watch the network tab, see what happens. And I'll be back in a minute.
RYAN: Yeah, we'll see.
JASON: Yeah, seems like we're good. I was just exploring the about page here and looking at the nested routes. So what I discovered is that we've got the parent page with about.jsx. I see this outlet that's coming out of Remix. What I've deduced by looking at this is that the outlet means if there is a sub-route that matches, so a folder that matches the name of the file, it will drop whatever the sub-route matching is into this spot. So by default, it hits index. And that's what shows up here. Then if we navigate to /about/whoa, it drops this content. Did I understand that correctly?
RYAN: Yeah, you got it. That's right.
JASON: This is slick.
RYAN: Here's what's really cool, too. When you navigate to a sub-route like that, if both of them have data, Remix knows that the parent route is still going to be rendering, just like it was before. It's not going to refetch the data for that. It's only going to fetch the data for the thing in the outlet.
JASON: That's slick. That's really nice.
RYAN: So we have a whole bunch of client-side data libraries that try to solve this problem differently. They're like, all right, what are all the queries on the page? Okay, we had to re-render. What are the same queries as last time? I'm going to pull those from the cache and anything that's new, I'm going to fetch. Remix can just look at the routes in the URL, and it knows just by the URL, before it even renders, what do I need for the next page, what's different, what's new, what changed. Just like React virtual dom diff. We look at the URL, what do we need to fetch, and then we can fetch them all in parallel because we know it all from the URL.
JASON: Perfect. I think that sounds great. Okay. So it is 23 minutes past the hour. We've got about 35 minutes to get something built.
RYAN: Yeah, let's load in your Sanity.io stuff.
JASON: I'm ready. I want to give this a try. I want to build a page that lets me post to Sanity so that I can add new entries and display my schedule.
RYAN: I guess let's first render your entries.
JASON: Yeah, okay. So I'm going to create a new file. Let's call this schedule.jsx. Then I'm just going to make some assumptions based on what I know about building with this style of framework, which is I'm going to export a default component. It's going to be called schedule. If I return a div that says schedule, my guess is that this is going to do what I want. So I want to develop here.
RYAN: We haven't even done that yet. Sorry. Yeah, go to development. You're going to have two terminal tabs.
JASON: Okay. So I'm going to run -- I wonder if -- we shipped an enhancement that should let this run with Netlify Dev.
RYAN: Yeah, not yet. Next release we'll have it.
JASON: So dev Netlify. I'm going to do another one over here. We'll go into GitHub, Learn With Jason. Let's learn remix. Then I'm going to run npm run dev. Oh, it didn't like --
RYAN: Oh, geez. Oh d you do an npm install.
RYAN: Local host -- oh, I don't know what the Netlify port is.
JASON: Looks like it started.
RYAN: Yes, you should go to /schedule.
JASON: Okay, we got a page. So did this start -- hold on. I got to look and see. Did this start in the -- it did start on 3,000. Okay, great. So here's our schedule. We got this thing up and running here.
RYAN: So now we can export a loader.
JASON: Is it a const or a function?
RYAN: However you want to do it.
JASON: Okay. And is this --
RYAN: Just an async function probably. Eventually.
RYAN: Then from here, this thing is going to get split out of the browser bundles and will only run on your server. But right now we could just return some random data. So just return like an array with an empty -- like whatever object in there.
JASON: Okay. So we know what the data is.
RYAN: There you go.
JASON: Can I just --
RYAN: So you're going to use a hook. We don't mess with your elements like that. That's hard to type with TypeScript. So you're going to bring in a thing from Remix called use loader data.
JASON: It autocompleted for me. Beautiful. Then does this give me just the data, or is it a deconstruct?
RYAN: Yep, data equals use loader data.
JASON: Beautiful. All right. So then we can just dump that.
RYAN: Budget debugger.
JASON: It's the only one I know. (Laughter) And there we go. All right. Now we have loader data. And this is not -- so if I view source here --
RYAN: Yeah, it's not in your bundle.
JASON: That's exciting because that means I can use privileged stuff in here. Like, I can make a secure call without worrying about exposing it in my bundle.
JASON: Oof, killer.
RYAN: So go to the Sanity docs on the API that I sent to you a little earlier.
JASON: Yes. So let me just really quickly link everybody to this. I manage Learn With Jason through Sanity. So we're going to just play with the Sanity data here. I'm going to go in and find this. We're going to be using Sanity's query. So we have like a query cheat sheet and things we need here.
RYAN: Yeah, copy that first one. Yeah, yes, right there. Just drop that into your URL, actually, in the browser. We can kind of see all this junk.
JASON: Okay. I'm going to have to swap out. I don't think this is going to work.
RYAN: Oh, is that your stuff?
JASON: Yeah, so this is their demo.
RYAN: Which that's fine.
JASON: Okay, great.
RYAN: Then let's get rid of the asterisk on their query. So open the URL and get rid of the asterisk -- sorry, the zero. Keep the asterisk. Look how big this is.
RYAN: This is huge, right?
RYAN: Anyway, we can go and fetch this in our loader.
RYAN: So you can literally just use fetch here. Copy and paste that URL and return a fetch.
JASON: Just straight up?
RYAN: Straight up. We poly fill fetch on the server so you can use it there.
RYAN: Congratulations. You just built a proxy, an HTTP proxy. You're now a back-end developer.
JASON: And here's the part that I think is really magical about this. What we just did, like, you're not going to hit cores errors on this because it's running on a server, not in the browser. You're not going to have to worry -- if you have to send an authorization header, you don't have to figure out hue to do that from the client side code, which you can't because you'll give away your secrets. There are so many cool things that just happen here, that just work. It's really, really slick.
RYAN: So what's kind of interesting here is we sent all of their headers to our app. Maybe we didn't want to do that. So usually if I'm fetching like, this I'll still do -- go back to your code on line five and let's do const response or res equals fetch. You know, the typical thing that we do.
JASON: Oh, yeah, yeah.
RYAN: Yeah, you can do it that way. You can do awaits inside too.
JASON: Oh, I see what you mean. I got you. So we can do that. Then come back out here.
RYAN: Now we're only sending our headers that we want. We didn't actually proxy everything from them. That's what we recommend to do. Even though it's a cool trick you can just say fetch inside of there, you probably want to unwrap it yourself.
JASON: Okay. So now what I have is -- sorry. I just got my --
RYAN: Oh, you got yours in there.
JASON: I got mine. This will pull all the data.
RYAN: Yeah, drop that one in there.
JASON: This is going to be enormous because it has transcripts for 250 episodes in it. So everybody buckle up.
RYAN: Before we send it, this is one of the really cool things I want to show you. Since we're not fetching this from the browser, we can control how much we actually send to the user. If you fetch this in the browser, you have no choice. You got to get that whole 800 kilobytes of Jason's JSON data.
JASON: Okay. So let's clean this up a little bit. Oops, what was that?
RYAN: So you can return.
JASON: Then we would get episodes. Or I guess it would just be data at this point. Await response. But then we can return a subset.
RYAN: Yeah, here we can filter and map and do whatever we want. So here, we have 200 episodes. Maybe you want to slice it first and take the first 20. So what'd they call it?
JASON: I called it data.results.slice.
RYAN: And zero, 20. Now you can map them. You can map the first 20 into only what your UI wants to show.
JASON: That's a good point. Okay. So let's go look at what we actually need here. All I really need is, let's say, the date. Let's get a title. Is it title? So much data. It's like multiple megabytes worth of data at this point. I mean, to your point, this is exactly why we shouldn't be just sending everything back. Why would I make you, the user, do a five-plus megabyte query?
RYAN: This is why I still want a server. I still to be able to give my UI precisely what it needs.
JASON: Oh, my god. Okay.
RYAN: Why don't you just send the first 20 to Remix and we'll console log it over there.
RYAN: So go down to line 19 and console log your data. So this will log in both the server console and browser console. Because we client render it.
JASON: So we're here. I screwed something up. Okay. So it was result, not results.
RYAN: Hang on. Let's go back to our UI. Look at that UI in your browser, the Remix one. There was an error. Did you set up an error page, Jason?
JASON: Sure didn't.
RYAN: Hey, developer, you should place this with what you want your users to see. Remix automatically catches errors on the server and in the browser and sends them to the nearest route error boundary. So each route, those nested routes, they can each say here's the UI I want to see if something blows up when I'm trying to render. We talked about those three things you have to write with a form.
JASON: I literally added a note that was like, I should probably error check this.
RYAN: And you didn't have to.
JASON: So it should be result. That should actually work. So let's go back, try again.
RYAN: And of course, if we don't want to keep fetching -- yeah, there go.
JASON: Okay. So then we can just grab out the title. Let's just grab the title and the date. So I'm going to map, and we'll get episode for entry and just get back the title, which is going to be entry title. And the date was entry.date. That should be a list of titles and dates, which should be way easier to look at. Boom.
RYAN: And what's interesting, this is your local computer. If this was out in the cloud, you bet your britches that Netlify or AWS' Lambda is going to be a faster connection to the Sanity.io to fetch that full four-meg thing. So way faster than your user. Yeah, we should totally be asking Sanity for less -- fewer records here. But even though we aren't, when you're actually in production, it's going to be fast on the servers. We only send a small bit to the user.
JASON: Yes. I should actually -- how do we limit down to just, like --
RYAN: Slice operations. That sounds right. Like 0..20.
JASON: We'll do 0..20, then we can skip this part. Just drop in the data. That should make the whole thing --
RYAN: Yeah, we just made our server way faster, but we still made it faster for the user by sending less.
JASON: Oh, broke it. Oh, data.result.
RYAN: That's why you need TypeScript.
JASON: Yes. (Laughter) What did I learn? Okay. But yeah, look at that. That's great. So we optimized for the browser. I also think there's a way that I could just pick fields.
RYAN: Yeah, Sanity has a great API, but not everybody did this for you. It's nice to have a server to do it yourself.
JASON: Absolutely. This is slick. Dang, that's -- okay, hold on. Does that really just work? Just going to take that out in case I need it. No, I broke it. What did I break? I need the type equals thingy.
RYAN: Oh, no. It's in between.
JASON: Oh, I literally broke everything is what I did.
RYAN: You still need the star. Yeah, there you go.
JASON: That should do it. Okay. We're also learning Sanity today.
RYAN: Yeah. Boom.
JASON: So that was really fast. We didn't query -- like, not querying that transcript is going to drastically decrease the size of the response. We could actually probably load all the episodes very quickly now because the transcripts are huge.
RYAN: Yep. And Sanity, they've got caches on their servers that's distributed. So you could run this up in production and get some pretty fast response times.
JASON: That's really, really nice. So the way that I would do this is we would want to get, like, anything that is -- like after today. So anything that's in the future. Do these have a date?
RYAN: There's only 200.
JASON: So let's get the date. So then we can get -- today is going to be new date. We will say schedule is going to be data.result.filter. It'll be episode and episode date, which we'll have to make into a date. Is this going to work? It'll be greater than today. I think we should be able to get all of them now because we're only getting the data.
RYAN: I think you still need the open/close thing. I don't know, though.
JASON: So I think this is the open/close thing. Let's try it. Let's see what happens.
RYAN: Just change two things at once.
JASON: Always. Always my first mistake. Okay. So this gave us -- let's see. I think I did it. December, December, December. November 30th. We did it.
JASON: We have successfully built a schedule. Could probably stand to sort these, but I don't care right now, so I'm not going to. I mean, this is huge. We just made, like -- we have server-side queries. We've got data filtering. And we're doing this in a way that's not happening on the client, which is a really powerful way.
RYAN: Yeah, go to root and search for header in here. You'll find it somewhere. Or search for link or something. Yeah. So right next to home. Let's link to your episodes or whatever we called this. Let's open the network tab and watch what happens.
RYAN: I'm surprised the live reload isn't happening for you. Maybe it's the Netlify template.
JASON: Could be a lot of things. My fans are also going at full blast. Could be my computer having trouble keeping up.
RYAN: So clear out your network tab. Then click on schedule.
JASON: So there's our schedule.
RYAN: Check that out. That's all we sent. And Remix did that fetch for you in the background. You didn't have to wire up any of that kind of stuff.
JASON: I mean, that's beautiful because it just takes out so much of the work that I would be doing. I think I still have -- oh, wait.
JASON: It's nice to know we don't have to think about it, right. Because that's the part that I always feel like happens. If you make something like offline performance or accessibility or whatever into a "we'll deal with that at the end of the project," what you're basically saying is we're never going to deal with that. I like that this just happens at the beginning. Very slick. That's nice.
RYAN: That kind of stuff and also air handling. That bums me out, when I'm like, I don't know what's going to happen if my app throws an error. What does the user see? We've already seen here a few errors. And Remix is already handling it for you. The way I like to talk about it Remix let use focus on the happy path. I don't have to put spinners in my computer, on my route component. I don't have to check loading states. I don't have to check error states. All that stuff is just handled for me.
JASON: Really, really nice. Okay. So --
RYAN: What did you want to do?
JASON: I was thinking we could set it up to actually post. So let's make ourselves a little form and see if we can post a new episode up to Sanity. So to do that, if I remember correctly, I need my form. Then down here, if I set up a form, my action is going to be a post.
RYAN: Method post.
JASON: That's the one I was looking for. Then we're going to submit to itself.
RYAN: Yep, we don't have to add the action in there.
JASON: And this is just regular --
RYAN: This is just HTML. Do your thing.
JASON: Let's see. It might not let me do this. Let's see if we can just do like a title and an input with an ID of title, a name of title.
RYAN: Name is actually critical for this to work in Remix as well.
JASON: Okay. We'll see if we can make this work. Some of the other stuff is complex. Like the guests are an array. I don't want to set that up. I'm going to see if we can get the title to save and pull that back.
RYAN: You could save that and submit it. Then you'll get a message.
JASON: Okay. Let's take a look. We've got this, and I have one coming up. Let me peek into my what's coming folder here.
RYAN: We don't have an action handler yet. Let's just hit the button.
JASON: All right. So test, test. And it did nothing yet.
RYAN: Nothing happened?
JASON: Also entirely possible that I didn't -- like, it's not submitting either, which leads me to believe I broke something.
RYAN: Button type equals submit. That's weird. Yeah, it's not doing anything.
JASON: Let me just peek in this actions, in case I missed these.
JASON: It showed up. Nothing is yelling at me.
RYAN: That's a bummer. Normally what it says is you tried to post an action -- a route that doesn't have an action.
JASON: So I need to add the action.
RYAN: Yeah, let's add an action. But it's weird it's not trying to submit.
JASON: Yeah, hopefully that's -- we'll see what happens here. And this was an async function as well. Or does it need to be?
RYAN: Yeah, if we're doing async work, it'll be async.
JASON: Let's see. We're going to submit to --
RYAN: Yeah, we should start out async.
JASON: And this is going to give me an event?
RYAN: Yes. So you're going to get this object in here. Let's destructure stuff off of it. We don't actually have a name for what comes in here. We want the request. So it's just called request. And this is a fetch API request. It's actually built into the browser. We put it into Remix.
RYAN: So what you're going to do here is get the form data off. So you can do const form data equals await request.form data. So there's request.json. That's what people are used to. Then there's form data as well.
JASON: So right now I could just, like, console log form data.
RYAN: Then you'll want to return something out of here. So we could just return okay true or something.
JASON: Does it need to be an object?
RYAN: No, that's fine. And we should be able to see something happen in the network tab. If nothing happens down there --
JASON: There it goes. Okay. So it did the thing. It submitted. We got an okay. Good.
RYAN: If you look at the payload, we'll see a title came through. And we logged on the server. If you open your server terminal, the Netlify one, there you go. Form data.
JASON: That's slick. That's really nice. Oh, it even did the -- I'm glad that didn't object, object on me.
RYAN: So now you should be able to get the form data. A lot of people say it's crazy, I learn so much about the web when I'm doing Remix. I actually spend more time on the mdn docs. This is a built-in browser concept.
JASON: If you haven't seen this one, this is a game changer. It simplifies form handling in React. Anywhere you go, this works. It's so much nicer.
RYAN: Yeah, so even though we're writing back-end code right now, it's all front-end APIs. So yeah, I think it's cool.
RYAN: So at this point, what are we going to do? Make a post to Sanity?
JASON: Yeah, we need to post to Sanity, which means I need to --
RYAN: I have no idea how this works.
JASON: I don't either. Let's see if we can do it. Maybe the cheat sheet has it. I don't know. I'll just be quiet now.
JASON: Okay. We're going to try -- are these all -- these are all queries. So we don't want a query. We instead want to -- okay. So we're going to send an auth tag. Let's set up the post first. I think this is probably going to be relatively close to what we want. So we can say like const response equals await fetch. Then we're going to pass this in, but we want to use, I don't know. I'm just using the fetch API. This is the browser API.
RYAN: Yep, but we have it on the server. You'll do headers with an object and whatever they told you to do over there.
JASON: Authorization. I've already set this in my -- crap, what did I call it?
RYAN: LWJ, I think.
JASON: I have it in my history back here. I just got to look at it real quick.
RYAN: I'm sitting on my son's bed, by the way, because the router is in their room in the center of the house.
JASON: Um, okay. Hold on. I should have written this down. Please hold, everyone. Sanity LWJ token. You are absolutely right. I should have just listened to you. Okay, let me close that, bring this back over. So now I'll drop that in here. What we should be able to do is pass in a body of whatever the query needs to be, if we can figure that out. I want a post, though. Mutate. That's what we want. So I want to mutate post data mutate. So this is going to be --
RYAN: I think you can probably just do a body and then do your form data or new URL search parameters. Oh, you want to do a JSON.
JASON: So it's going to be /data mutate and data set name. Which I think is -- I guess we'll just try one of the types. So let's get this. Oh, data set name, production. Because you name your data set. That's what it is. And I need to create a document. The document is -- oh, boy. All right. We can figure this out. We're going to -- data binary. If I put data in here, it's going to be -- no, it was mutation.
RYAN: Yeah, they have this root key of mutations.
JASON: Then inside I need a transaction. The transaction is going to be --
RYAN: You have some weird syntax going on there. Your mutation is an array, then you have this string floating out there. There we go.
JASON: Corrected. I don't think we need an ID. It should auto set, but I'm going to create a type. My type should be episode. The title should be title. So maybe we just try this and see what happens.
RYAN: So if this works, then you might just want to redirect back to where you are, right? Or send a message. Whatever you want to say.
JASON: Okay. So let's assume what I want to do now is just log the response down.
RYAN: We got to return something from our action then. You can just keep returning okay. We already got it at 47. Sorry.
JASON: So I need to get this out of here because I'm doing all the things wrong. Now I'm outside of my fetch request. So theoretically speaking, this will work, assuming I remember anything from when Cap from Sanity was helping me do some post stuff.
RYAN: Yeah, let's console log await response.text, just to see what it is. Oh, they're probably going to give you JSON.
JASON: That would be my guess. Let's try it. So let's see what we get. That's rebuilt. So if I come back out here, it did a post. Sent back okay.
RYAN: Your server terminal is going to have your console log.
JASON: It says unauthorized. I bet I need to restart.
RYAN: Okay. Well, all right. You probably want to just finish this. I was going to show you a cool feature of Remix. But let's keep going.
JASON: Yeah, let's see. We might run out of time, and it might just be that I'm not doing a good job of writing this thing because this session is not found. These are all bad things that make me believe that I'm not actually getting my --
RYAN: Token in there correctly. Let's double check the bearer syntax. Sometimes there's a colon after it.
JASON: Yeah, let's see. The bearer token was --
RYAN: I think it was right there in that example. Yeah, they got authorization. Yeah, just bearer token.
JASON: Makes me think I'm doing something wrong.
RYAN: Did you spell authorization right?
JASON: Who knows. I'm actually wondering if I should be -- so I am supposed to post. We are supposed to go to data mutate. And I sent in authorization bearer token, yep. Sent in my mutations. But I'm doing something. Let's just submit again and see what it does. And if this fails -- yeah, it's not getting my token, which means I set that up incorrectly.
RYAN: Let's do something fun then.
RYAN: So go back to your action. Where it says your response -- after the response, where you console log it. Say if response.status does not equal 200, throw new response. You can have an empty body in there. Or just say unauthorized or whatever. Or what were we getting, 401?
RYAN: Say equals, equal, equals. And do an object in here and say status 401.
JASON: Okay. I don't need to return after a throw, right?
RYAN: What's that?
JASON: I don't need to return after a throw?
RYAN: Yeah, so what we're saying here is we can't finish this. We can't render this.
JASON: Look at it go. I mean, that's huge.
RYAN: It is really huge. So in Remix, when you try to get a record from your database or interact with an API or something like that and you can no longer execute code because you're not authorized to do this or this document doesn't exist or whatever, right. There's a bunch of reasons why you can't finish rendering this component. So in Remix, at any point you can just throw in your loader, all code stops executing, and we end up in this place we call the catch boundary. If you throw something, a response, Remix will catch it and render the catch boundary. So once again, you can keep your happy path happy. So if you got an error, we'll catch that, render the error boundary. If you have a client error, you throw it, we'll render it in the catch boundary.
JASON: I mean, this is incredible. I feel like we could go for a long time. I feel like we barely scratched the surface here. But we covered so much, you know, just the amount of things that we don't have to think about, right. So I'm very excited to keep digging in on Remix. I hope that y'all are too in the chat. But that's all the time we have for today. So if somebody wants to go deeper, where do you recommend they go next? I'm going to throw them at the quickstart tutorial here. Anywhere else?
RYAN: The quickstart and then -- I mean, that jokes app is pretty intense if you want to go deep. Go for that. But we've documented the heck out of this so far. So the API docs on the left, conventions and Remix package f you click on those, they're really big. We got a lot of information in them. The Remix package especially as more. Those are the two docs that you'll look at a lot. Join our Discord and people will help you out. Yeah, you can do a ton of stuff with Remix.
JASON: That is great. And then if somebody wants to find the Discord, where do they go for that?
RYAN: That's also on the docs home page.
JASON: Community. Here we go. So Discord server here. Just hit that docs page. You'll find everything you need there. Everybody make sure you go and give Ryan a follow on the Twitter. You can also follow Remix on Twitter, if you want to keep up with updates. With that being said, I'm going to do one more shout out to our sponsors. We've had Rachel from White Coat Captioning here today doing live captioning the whole time. Thank you very much, Rachel, for being here. That's made possible through the support of Netlify, Fauna, and Auth0, all of whom kick in to make this show more accessible. We have a lot of fun stuff coming up on the show. Make sure while you're checking out the site that you take a look at the schedule. You can add it on Google Calendar, all those good things. We have a ton of good stuff coming up. Make sure you go and add that to your calendar. With that, Ryan, any parting words for everyone?
RYAN: Go build better websites.
JASON: I love it. All right, y'all. Stay tuned. We're going to find somebody to raid. Thank you, Ryan, for spending time with us today. We'll see you next time.
Closed captioning and more are made possible by our sponsors: