Remix is a web framework that gives you the simple mental model of a Multi-Page App (MPA) but the power and capabilities of a Single-Page App (SPA). One of the big challenges of SPAs is network management resulting in a great deal of indirection and buggy code. This is especially noticeable in application state which Remix completely eliminates, but it's also an issue in individual components that communicate with a single-purpose backend endpoint (like a combobox search for example).
In this talk, Kent will demonstrate how Remix enables you to build complex UI components that are connected to a backend in the simplest and most powerful way you've ever seen. Leaving you time to chill with your family or whatever else you do for fun.
Transcription
you what is Up remix friends? I'm so excited about remix conf EU and I'm really excited to be speaking with you all about full stack components. So let's go talk about taking cool location to the next level. Just the one thing that I wanted to talk about really quickly is I'm working on epicweb.ev if you haven't seen that yet definitely give it a look. It's my full-time thing. Now, it's awesome and all my slides and everything for this talk are on my GitHub so you can take a look at that there. So this talk is going to be a demo of components that include both the UI code as well as the server side code and we're gonna be doing a lot of coding and so yeah buckle up, we're gonna be talking about remix, of course our favorite UI framework and well full stack framework, not just UI and that's part of what this talk is all about. So remakes allowed us to marry the back end and the UI in a way that has never been done before with the loader and action and our UI all in the same file and this A pretty simple demonstration of how that works from a route perspective. So we have our project's route and here we have our loader to load those projects and we've got our form to add new projects and the backend piece for that mutation. But sometimes we have uis that aren't like so you are URL Centric for example, the Twitter like button whenever I click on that like button that's not going to take me somewhere else. I don't only render that on a special page and like that it has a specific route. I render the like button for every single one of the tweets that are on the page. And so that doesn't really work very well as something that you would like stick in a loader or an action for a particular round necessarily and here's another example of a combo box that's doing a search and this is the thing we're actually going to be demonstrating today. So I've got an implementation of this app right here. We're not. During the combobox yet because we are going to build it together and connect it to the backend and it's going to be sweet. So that's it. It's demo time. So the first thing is we've got this app up and running right here. We're in the zero one before version of the app and we're in the app directory under routes and under this Resources directory is where we'll find the customers. So we're going to have this route for slash resources slash customers. And that's going to be the API route that we're going to be using to go get a bunch of customers. Now one really cool thing about remakes is that if you don't have a default export of your module the remix will treat your module like a resource. And so what we mean by that specifically is I can say export and async function whoops a function come on there call loader and here I'm going to return Json and that Json actually is going to come from remix run node. And we'll say hello. World and if I save that and come over here, I can go to resources customers and I'm going to get hello world. Now. There's nothing special about the Resources directory other than the fact that my editor seems to like giving that a special icon. But there's nothing special about this we could call this whatever we want and it just so happens that the way I like my URL for this to work is to have a slash resources and that's it. And so the wherever we want that URL to be that's where the file is going to be and so by having a loader export but no default export. This is just a regular request for like an API request. So with that in place now, we can build some UI that inner interacts directly with this loader that makes fetch requests, but what's really cool about remakes is that we can actually add a bunch of other exports to this as well. We can export const koala equals Cody, like it doesn't matter we can do anything that we want to in here and remix just ignore it and it's builds. And so what that means is we can actually export a component in here that consumes this loader and that's exactly what we're going to do. And because I know that you probably don't care to watch me right a bunch of jsx. I've actually written all the jsx stuff there. So here's our loader. It's just what we had before but then we got a bunch of other jsx stuff in here that you like. I said, you probably don't care to watch me do all that so skipping over the jsx bit the most important and interesting bit here is we're using this use combo box, which is a downshift hook down shift is a library built years ago when it was at PayPal and it's for making this combo box experience for us and all we need to do is provide it with the items and we can respond to input value changes as the user is typing. So that's perfect. That's exactly the two things that we need. And with that we're exporting this combo box, but we're still just a resource route. There's nothing special going on here. This is literally just hitting an endpoint and getting whatever the response that we sent back was. So let's do a couple of things with this endpoint though. So we'll flesh out this this loader. So for one, I personally don't whoops. I personally don't really want people to be able to hit this endpoint who are not authenticated. So we're going to say require user And we'll pass the request and we get that request in our loader, of course, so we'll pass that request. We'll use the loader. Args, there we go. And with that now I can still make a request to this. But if I go Incognito I'm going to get redirected to the login. So that's nice. Remember all of your loaders are basically an API endpoints. Anyway, anybody could curl that URL and get access to everything that you have it that URL. So you do need to protect your loader data and this is how we're going to do this here. So the next thing is I'm going to get the URL from the request that URL turn this into your url object so I can get the query from URL. Search params dot get and we'll just do full word query here. And we want the query to be either in this case. It's either a string or null. We'll go ahead and default that to your string. So it's always going to be a string. So now I can do a search customers and this is implemented using Prisma and sqlite is how this is set up. But this search customers could be an implementation that like makes a fetch request to another Downstream service or like a rails or PHP backend or whatever that parts are not important. Just the fact That we have some mechanism for sending this query along to where we're going to actually query for the customers. So we'll pass this query along and we'll get our customers. These are the matching customers back. And then with that we'll send that. In our response. So now if I hit that I'm not gonna get anything if I say query s that I'm still not going to get anything because I messed something up. So no that that is the correct query and query right here and query right there. Oh, this is not good. So we're gonna You are all search params. Oh, shoot. I'm gonna have to start over. Oh, I know what it is. I'm not gonna start over. I forgot to await silly me. So now we're gonna get the customers. There we go Okay. So we've got all of our customers there. Then we can query for specific ones and we can just get all like an array of all the customers that match. So that's exactly what we're looking for for the API round. Now, here's the Special Sauce. This is the what's going to actually connect us to the UI piece of this is our use fetcher. So Cody the koala. Hello. Here's Cody is going to tell us we need to implement fetcher here. So we're gonna say fetcher or we'll call this search customer Venture. You can call whatever you like. It really doesn't matter and we'll say use fetcher right here and then to get the data that this fetcher is going to be interacting with to be fully typed. I'm going to say type of loader. So there's my loader right there. I get type safety for my search customer fetcher and that search customer fetcher is going to have a data property on it. And if that data property exists, it may not because on the initial render, we're not like calling this loader right from the start. We're calling it when the user is searching right? And so if there is no data, then that's going to be undefined. And but if there is then we're going to have customers so we'll have data. Elvis operator customers and if that doesn't exist, then we'll have that those customers be an empty array and with just that change now a typescript is a lot happier because this type definition right there. Our customers is going to be an array of customers and now this this type is correct. And so our selected customer is going to have a correct type and the type we pass along to use combo box. So that's really cool to be able to have full type safety across the network for a component that's going to be used all over the application. Who knows where it's used. So the last piece of this is actually making the request and for us to actually make this fetch request. And we just want to make a request whenever the input value changes and downshift provides this API for us to know when the input value changes. So what we'll say is the search customer fetcher and there are a couple options on here. We have dot form which we could use if this was a explicit submission that the user is like clicking on a submit button. That's not the case for us. This is an imperative mutation or an imperative query that we need to make as a result of some other user interactions. So we can't use form. There's also load which we can use to make a get request with query prams, but we have to serialize those ourself. So I don't want to do load I'm going to do submit just because this makes it a lot easier to serialize those query params and actually GitHub pilot is pretty close here. The this is complaining right here because the query can only be a string it can't be assigned undefined and input value on the changes that we're given is optional. It may not be So we'll just add a I forget what that's in text is called a coercion or something something there if intimate value is no or undefined then we'll end up with an empty string now the last piece we need to add to this is we need a method of get and that's it or lowercase get here and that is the case because we have implemented this as a loader now, we could actually implement this as an action and have our method be really anything but get like a post or a put or something but I'm happy with this being a get and then and like for query and points. That's typically it makes most sense to be a get. But yeah you do you however you want to the cool thing is that you don't have to jump into 30 different files or or multiple repos to make this change. You can just one day decide. You know, I want to do this as an action instead and just switch it right here in the same file, which I think it's rad. Okay, and then the last thing is the action now this action is the URL that is going to be hit with this query and the URL is the URL for this loader which we've already established is inside of resources customers TSX. And so we're going to say slash resources. / customers, okay, and that's it. We have finished this feature and it totally works but we need to actually render this component and this is this is the pretty cool part. So like so far this kind of feels a little bit just like your typical remix route and everything because you've got your loader you've got your type of loader like everything feels very similar to what you're used to in building a remix round. But the cool part is that we can actually use this component in anywhere in our application and it will stay connected to this back-end loader. So in particular the part of the application we want to use it in is inside of our sales and voices new when we're creating a new invoice so we can choose which customer we want. And so if we come down here, we've already got Cody helping us out with the props that we need to pass and everything. So we've got our customer combo box, which we are going to be importing from routes resources customers. So typically I don't recommend doing any imports from the routes directory in general. I just think that is not A really safe way to do things. But this is one exception that I think is is totally legit and it is totally awesome. So that's all that we're gonna do in the customer combo box piece here on the new route and with that in place if I come back here and go create new invoice, then I've got my customer combo box now we currently have this set to true but the and we can fix that here in a second. But the cool thing is that this functionality currently totally works as I make changes and I can select customer and all of that stuff. So that's that's pretty random. We've got a component that is connected to its back end and all in a single file. So the last thing I want to do is just fix the spinner because that is bothering me and so if we come down here, we've got our show spinner and our pending state is going to come from our fetcher. So we'll have our search customer fetcher. You gotta spell it right? There we go. Search customers fetcher dot state. And the state can be idle loading or submitting pretty much if it's loading or submitting we know that we're in a pending state. So normally I just determine whether we're in a pending state by saying is the state not idle if it's not idle then we're pending there is a subtle difference between submitting and loading where loading is going to get some data submitting is when the user submitting data, but in our case, I think it's reasonable just to say if the state is not idle then where you can show the spinner. So with that in place now we can. See that there's no loading state. But if you watch carefully you might actually see a flash of loading State and that is not awesome. So there are a couple options here. You can add like a transition delay or something. But the problem is if the the request takes like within, you know, 50 milliseconds of whatever transition delay you put in there. You're going to get a flash of floating State. It's really basically impossible to avoid without a little bit of extra help. So basically what we need is to say don't show the loading State unless you know, let's put it delay in because that does make sense. Like let's if it's within a hundred milliseconds, you know, don't show any loading State at all. But if I do show any loading State then we should keep the spinner hanging around even if it's finished. So keep the spinner hanging around for another like 300 milliseconds or something like that just so that we don't have a flash because it's it's way better to show it for longer than you need it then to just show a flash of loading State and there's a package built specifically. Solve this problem is actually built for my website by Stefan Meyer who helped with implementing my website and it's called use spin delay. And we'll pass that along there. It has some good defaults. We'll just keep those and with use spin Delay from the spin delay module now, we're going to avoid that flash of loading State and if we do have a slow 3G network. Then we are going to get a nice pending state which is exactly what our users would expect. So with that in place, we we're solid like this is this is really really great. You could render this component all over the app and be certain that it is and connected to its back end on top of this you can actually also package this up in an npm package and then expose just a couple of things for people to go into their remix config add their routes option here so that they and they call into your your thing so that you can hook up the route for the API route and then they can just start using your component and it's all magically connected to the back end, which that's that's pretty interesting and I'd love to see somebody try to do that. So with all of that said, let me wrap up with a last couple of thoughts. So interview remix allows us to create resource routes by Simply Having a route with no default export. That's what makes a resource route. We can export anything. We like as long as it's not a default export so other components and utilities that integrate with the resource route directly and so it's not just components you could do hooks. You could do really anything like a button that links to a PDF generator and the loader is like generates the PDF whatever anything that ties specifically to this resource and and just keep that co-location and and that code location makes it easier for us to maintain the software in the long term. There's a really really cool and exciting. I love this. So the last thing that I want to say to you all. Is your inspiring thank you very much. See.