Conf42 JavaScript 2023 - Online

The Remix Philosophy is not Just for Remix

Video size:

Abstract

We are moving into a world where rendering and routing are best handled by a combination of server-side and client-side code. Remix has taken a robust approach they call the “Remix Philosophy.”

This talk includes:

  • Intro to Remix

  • The why and how

  • Applying the Philosophy outside of Remix

Summary

  • Ken Snyder shares his journey of using Remix in his web app application. First, why I chose it for my project. Second, I'll introduce you to remix by creating a small app. And finally I'll talk more about remix philosophies and approaches and how they can be applied outside remix.
  • Ken Snyder: This presentation is on Remix, which is a meta framework. He says it doesn't replace react or code, but sits on top of both of them. Snyder: Let's go ahead and create a remix app.
  • App allows you to really organize your code when it comes to parent child relationships. If you're interested in doing more with this particular code, you can try it on GitHub. outlets can be representing kind of your series of URLs. It's a very simple and nice little app with just very few lines of code.
  • We are working with web foundations. The way to write server and client code is very simple. We'll also talk a little bit later about how these kinds of applications can work without javascript. And we'll talk about the roadmap and the future flags.
  • A remix application is able to grab the data, generate the HTML and return that in the initial response. What will be at the bottom is this client bundle, and its purpose is to hydrate the page. There are 116 repositories right now of stacks that people have created.
  • remix uses both server side and client side, the same fetch API as of node 18. We also saw how you can use form data to serialize forms. remix also takes advantage of some prefetching and caching that's built intro the web and built into browsers.
  • remix recently came into version two. It allows you to get new features faster and allows you a lot of freedom in terms of when you want to update these breaking changes. When I write remix route, I don't have to worry where my code runs.

Transcript

This transcript was autogenerated. To make changes, submit a PR.
Hi, my name is Ken Snyder and I want to share my journey of using Remix in my web app application and why you might love it as much has I do. I'll cover three things in this presentation. First, why I chose it for my project. Second, I'll introduce you to remix by creating a small app. And finally I'll talk more about remix philosophies and approaches and how they can be applied outside remix let me introduce myself. My name is Ken Snyder. I founded the company Shoreline, which offers a web application that allows cancer doctors to provide patients with specific and well timed educational resources throughout the treatment process. I am a co founder of UtahJs, a group for JavaScript engineers to get together for lectures and networking. And this presentation is on Remix, which is a meta framework, meaning that it doesn't replace react or code, but sits on top of both of them and really not just node but any javascript or typescript runtime for us. We are in the situation where patients are viewing education information on slow cellular networks. They're looking at only one or two pages and we want them to see the content as soon as possible and interact with it as soon as possible. Remix comes from the minds behind react router, which is the gold standard for building a react app, and remix is actually a company. It's staffed by full time engineers. Earlier this year they were acquired by Shopify, and so they have a lot of attention and resources driving this framework. So let's go ahead and dive in and create a remix app. A really easy way to get started with Remix is to use MPX create Remix. You give it a directory and you can also pass it a template and it will create a new application for you using that template. There's over 100 templates in the community and this case we're just using the basic default template, but we can go ahead and open vs code to that folder and as we go in we can run NPX, run dev, and that will create a server for us. It will add a hot module reloading and as we visit this new application we get a welcome screen. This is defined in the routes folder as underscore index that represents the slash route or the root route. And in here we're going to go ahead and create a new page and it's going to be shop and so we create a file inside of the routes folder called Shop TSX. With that all we have to do is define a component and whatever's in that component will get rendered at that URL. In this case we're just creating a header so that we can see and demonstrate that we've got this route working. And then the second thing we're going to do is we want to create a page inside of this shell. So down below the header we're going to create the list of shopping items and allow us to add things to the list and to put things in our cart and to delete them and to just have this shopping cart kind of application. So we're creating this outlet and inside the outlet we want to render a child page which is shop items TSX. So we put that in the routes folder. And now whatever we return here from this component will be put and rendered at shopitems. So we'll go ahead and create just a hello world page so we can see that it's working. And then let's go ahead and load some items in. So we don't have a database but we're going to go ahead and just load items from memory. Here we're going to use this function called JSon. It's a remix function that just creates a response object with content type JSon. And this case we're passing it in one piece of data, which is items. And instead of items getting from the database like we might normally here, we're just going to define some in memory here that will sit with the server. The id is just the new date. So normally that would be an id from the database, the name of the item, and then we're going to say that it's not in the cart yet, we've just put it on our list. And these need to code into our items component. And the way we pull them in is by using this function, use loader data. So one thing that's nice about use loader data is that you can use type of loader to tell typescript what data is going to come in. So we know that the items have the shape defined in line four and those items come out of the loader function using the JSON function. And so when we tell typescript to use the type of loader, then in our items component here we actually get a list of items and we know exactly what types they are. And so we get that nice transition of types between server and client without having to do any extra steps or define any schemas or sharing and defining lots of types. So we're just going to go ahead and print out the items using a pre so that we can see them come in. And then we're going to make a way to create an item. And with that all we're going to do is create a form. The form is going to have one thing which is an input for the name and then a submit button. So we can go ahead and see in here. If we add something and click add item, we've got a 405 because we don't have any way to handle that post yet. The way to handle that post is just an export function action. And the action basically will respond to any posts or other verbs, other methods. And in this case we are reading the form data object. So because it's a form post, it's sending a form serialized in the normal browser, HTML form serialization, and we can pull that data out has a form data object by awaiting request form data. We're going to pull out the name from the form data which is the name of the item. And we can do that by just calling get name and we should have a string representing the name of the item. And we don't have a database here. But again, we're just going to add the new item with an id, a name and represent that it's not in the cart neither. So as we do that and we refresh, we can see, oh shoot, it still doesn't work. What do we need to do for the action? Well, it says you have to return something from the action. So in this case we don't really need any success messages or additional data sent back to the user. We just return null. And whenever we do this remix is automatically calling the loader again to get the new data. And so if we type in a new item and click add item, the way it gets added is that it's going to go into the action, push the item and then it'll go into the loader and load the item and rerender the component with the new items. You'll see here. One of the problems is that the text stays in the text box. We'd love it to clear out that text so we can add an item over and over again without doing anything special. A really simple way to do this is to force react to rerender the form anytime the items or the number of items changes. And so that will actually clear out our form anytime that we type something in. One thing though, if we type in and press enter, we're not going to be in the input anymore. So all we have to do is do auto focus. And now the browser will automatically keep focus into that input as we add more items. So let's go ahead and actually take the items and display them in a reasonable way. We're going to use an unordered list and each of the items will be a list item. Here we're using the id has something unique. Again, this might be an id from database and then we're just going to print out the item name. So you can see here we've got a nice unordered list with our items on it and now we want to be able to delete items off of our list. And so one way we can do this is do a post to shopitems and the item id. And we're going to go ahead and display it inline just for this demo so we can have the button next to each item. And you can see we're doing something special with this button. It's not a normal submit button. It has a name and a value. It turns out in javascript and in HTML you can just give a button a name and a value and when you click on that button it's as if that form contained a hidden input with that name and value. So this will allow us to tell that the delete button was clicked. When the form gets serialized it will have one value with the name of operation and a value of delete. So we need to go ahead and handle that form. And so we're going to create a new file in shop items dollarid. What that does is create a new URL for shopitem and then the id of the item which will allow us to mutate that particular item. The dollar id represents the name that the param that will come in and so we can just read the params inside the action. So we'll read the iD, we'll parse it as an integer and then we'll take the form data from that form and deserialize it. So we just have the one thing that we're interested in, which is the operation, and we're going to handle when the operation is equal to delete and we're going to just act directly on that list of items that we defined in our other route. Normally you would be doing some sort of database manipulation here, but anyway, what we're going to do to delete item is just find the index of the item with that particular id. And if it's found we're going to splice out the items at the given index. And as we learned before, with actions you've got to return something. So here we're going to return null. But now you can see if we type in a new option, we can add it and then we can also delete it. So let's add the ability to mark it as loaded into your cart. We're going to use another button. We don't have any more hidden inputs. We're just adding a new button with a name and an operation and the value of load. And that allows us to create a second mutation which is to load it into the cart. And we're going to represent loaded items by just giving them a line through text decoration so we can see the item is still there and that we've purchased it or that we've got it in the cart but it's not deleted from the list yet. And so here we're going to handle that load operation. And before we do that, I've realized we can refactor a little bit here. We're always going to be operating on one item. So let's find the item first. And then if we delete, if we need to delete the item, we splice it. If we need to load it, all we're doing is saying that it is in cart of true. And so as you can see, we can add an item and we can load it in the cart and it will have that nice strike through on it. Last thing is be able to unload it from our cart. So we're going to create a new button that does unloading. And again, it's going to come in here to the same action and we're going to handle the case when operation is unload. And it's very similar. All we're doing is marking as in cart as false. So now we can add items, we can delete them, we can load them in our cart. We can unload them in our cart. And now we've got a very simple and nice little app with just very few lines of code. If you're interested in doing more with this particular code, you can try it on GitHub. I have it at this URL and this QR code. One more thing I kind of want to mention about outlets. So outlets can be representing kind of your series of URLs. So in this particular app here, we have some general information like the header that comes across the top and that can be represented at the root route. And then when we load projects, we're loading this green area, which is a list of projects. And if you click on a project, then you'll get to the slash 1234 part of the URL and that will render inside of the green which is represented by red. So it allows you to really organize your code really simply when it comes to parent child relationships. And of course you don't have to do that, but it's a nice feature for those kinds of situations. You might say the forms that we dealt with were really simple and let's talk about something more complex. So here is a user's table. How in the heck would you represent this with just form actions? Normally your first thought would be okay, we're going to add a click handler to each of the pages and we're going to add a click handler to the sorting and we're going to capture the submit and apply the filters. So let's take this and we'll say that the form is this box right here and that these are actually all buttons. So we don't have any on click handlers, we just have an outer form and inside the form we have all of the submit buttons. So the filter will be just a regular submit button. So if you type something into search, you click filter and then it's going to go ahead and post that and you can do a search. And then we're going to use our trick that we learned from before, which is that buttons can have names and values. So the sort buttons, for example, this one is the email address. To sort upwards or ascending, you would send the value for sort as email and you could represent the descending with email or email descending or however you like to do it. Then the pages, they represent a page and a value. And so for example, if we were to make this a get form, you could see in the URL that we would have the name page equals two and sort equals email and filter equals bob. And that would all be in the URL. It would work right out of the box where it would search immediately if you went to that URL. Or it would just update the URL as you're going along clicking on these buttons. Let's talk about the remix philosophy. So as we saw, the way to write server and client code is very simple. We didn't think a lot about what runs on the client and what runs on the server. We are working with web foundations. The requests that come through, for example, are regular request objects and the responses are regular response objects. And we'll also talk a little bit later about how these kinds of applications can work without javascript and why you might want it to work without javascript. And we'll talk a little bit about the roadmap and the future flags, concepts that they embrace. So talking a little bit more about the server client model, basically I'm kind of person that's interested in how in the world does this get bundled. So we saw that there were a loader, a component and an action. And in doing that, how is the bundle created for the browser, and how is the bundle created for the server? So this is basically how it goes. If we look back at our shopping cart application, all of the things are in the server bundle. So everything gets bundled on the server and that makes the server capable of fetching data and writing and generating the HTML and then the browser, all that it needs is the part that generates HTML and the remix runtime which will automatically handle form submission and navigation and load things via fetch and kind of comparing it to a single page app. What you traditionally would see is the HTML first comes down to the browser and then the browser sees that it needs to download a bundle and so it has to go back to the server to get the bundle. The bundle will finally tell what data needs to be fetched and then when that data is returned, it can generate the HTML. Compare that to a remix application where on the first load the server is able to grab the data, generate the HTML and return that in the initial response. So immediately the user can interact with the page. What will be at the bottom is this client bundle, and its purpose is to hydrate the page. And by hydrate we mean make additional interactive things work. So with forms it will automatically handle them and submit them via fetch. And for links it will use react router to load only the parts of the page that change. So it adds efficiency. But it's not absolutely necessary for the application to work. And one way to maybe visualize this is to think about a waterfall. With remix, the page is fully painted and you can use it, and then by the time all of the hydration occurs taken, it's fully interactive in terms of anything that's special to JavaScript. So for example, if you've got Tinymce, obviously remix can't completely paint that from the server side because it takes a lot of client side JavaScript to make the tiny MCE work. And you can compare that to single page apps where it takes multiple waterfalls until the page is even painted. One really great feature of remix is that there are lots of server and runtime adapters. So you can use new runtimes such as Cloudflare workers, Dino and Bun, and you can use services like Netflix, Azure and AWS. It supports all of those things. And also you can have actually just more traditional server frameworks, including exprs, fastify and many more. And if you actually look at the official list, there's 116 repositories right now of stacks that people have created. And these are various combinations of technologies. And it's really great to see people from the community creating these stacks and making remix work in so many different ways. And I do want to just highlight the fact that we've got some really great concepts in Dino and bun that are coming out and code as it's getting to version 20 and 18 and 20. There's call these amazing new things, and I love that we can just pick Remix now and it will work with all of these things going forward. So the second thing to mention is that we want to understand and work with the foundations of the web. So you may be familiar with this episode, this reference where Roy and Moss get together and convince Jen that this little box is the Internet. And so we have a bunch of great standards. As I mentioned, requests and response are things that remix does right out of the box. We also have both server side and client side, the same fetch API as of node 18. And basically remix uses that fetch mechanism internally. We also saw how you can use form data to serialize forms, and a few other things that we'll cover in a bit here. Headers, how to read headers and set headers, how to work with URLs to parse them or to build them, and the parameters to serialize and unserialize query parameters in a URL. And then remix also takes advantage of some prefetching and caching that's built intro the web and built into browsers. And we don't know a ton about these, probably because we're not used to making use of them. But Remix has kind of done the hard work for us and automatically uses prefetching and caching in its mechanisms for adding those features. And then we also talked about HTML forms and how remix embraces those in order to do any kinds of mutations. So just to kind of give you an example, the way request and response both work on server and on client, we have these newest runtimes, Bundino Cloudflare workers, for example, they all use a raw request and they all expect you to return a new response. So these are really interesting primitives. And obviously these functions here that fetch and receives a request, that's where you would add some sort of adapter that does the routing based on the request URL. And that's sort of how remix can plug into each of these. And you may not have realized it, but when you're using fetch, you're really using request and response. So fetch and request have the exact same signature. You can take a URL and then you can also take an initialization where you can set the method and the headers and the body of the request. And so what you really can actually do is just create the request and you can send the request to fetch. So that's an alternate signature for using fetch. And one way to think about it is that fetch takes in a request and returns a promise with a response. You can also use headers when the request comes through. It has a headers property, and this is actually an headers object. And headers has things like get and set. Get allows you to get the header regardless of uppercase or lowercase letters, because the HTML standard headers are not case sensitive, so it's easy to work with them. If you just use the get and set methods, you can create a new headers object by just passing in plain JavaScript object, and the key value pairs will be used to create the headers. If you need headers, for example, that repeat. So if you have two cookies that you need to send, then you have to have set cookie twice in order to do that. You can't use a plain JavaScript object, but you can send an array of arrays. So in this headers object we have three headers. One is content type, one is set cookie, and another is also set cookie with a different cookie. In a similar way you can serialize and serialize search parameters. With the URL search params constructor you can pass in one of three things. The first is just a regular string that you might see in the URL. Second is a plain old JavaScript object that will have the keys and values as properties and values in the object. And in a similar way you can handle when search params have more than one of the same thing. So in this example maybe we are doing a search form. We're filtering by the word hello, we're sorting by name, and we're saying that we want things that match either the tag JavaScript or the tag typescript. You can use things like fetch and form data in a traditional react component. So in this case we have an add user page where you can type in first last email of the user. We're going to actually take the submission and prevent the default submission from happening. In the form data constructor you can pass in the form element itself, and then you can deserialize it by using object from entries, and that will get the form data out into these variables first, last and email. And then you can do more of a traditional kind of post to a server or API where we're going to post it with content type application JSON and we're going to send that body JSon stringified with the three values that we have here. Now what's interesting is that you can actually do it without serializing into JSON. So if your server is prepared, you can receive the form data object as an object and just pass it in that way and so you don't have to destructure the form or serialize it into JSON. And you may have noticed here that that's what remix is doing as well. In our examples, the form data we were getting from the request object, and this is what it looks like on the clientside, sending that up. Or you can just use remix. So the nice thing about remix here is that we don't have any logic around submitting. We can just have a form component and it will automatically capture the submit, use a fetch and navigate to the correct place after that. As I mentioned before, there are some times that you may want your application to work without javascript. So in our case we've got patients coming to look at some pages and we love this idea that the first render they get all of the HTML, but also for forms that are in there, they still work before hydration because they act like just regular HTML forms and submit to regular URLs. The other reason is we ran intro a need for supporting IU eleven. So one of the electronic medical records providers, very popular one, they actually today are selling their desktop app with integration ability only available through ie eleven, kind of framed in thing inside the Windows app. So we have a need for our application to work with ie eleven. And if you look around at a lot of the popular frameworks and everything that we've been using, we're thinking we're going to have to check every one of our dependencies. We're going to have to find out which polyfills we need to use, which ones don't have polyfills, which ones just can't work at all on old browsers. And that's going to be really painful. So I first started out by going to remix's website and saying, hey, do they have support for ie eleven? And they're like, well, we use script type module, so anything that's a little bit older than three or four years ago won't even be able to load the javascript because those browsers only know how to download scripts that are script type javascript. So that worried me a little bit. But then the following sentence was talking about how you can actually make it work with Netscape or any browser that's ever been made, because you're just using regular forms and regular navigation. And so let's take a look closer at what our example application looks like without JavaScript. So back to our applications. We can open the root TSX file and you'll see in there that it has a reference to this special component called scripts. And if we take that out, if we comment that out, basically what we're going to end up with is our application will have no javascript and you can see there in the network tab that we don't have any network requests going on. So you can add items, call you want. What happens if you go to delete the item? Oh no, we've got this returning null. Well, what we should have done in our action is actually redirect. So you kind of have to think back to fully multi page app to how those actions should redirect. But once we have those, it is reloading the entire page each time. But this form situation is working wonderfully for us to do whatever kinds of additions or changes we need to just with regular forms. And then the last thing that I'll mention here about their philosophy is the way they do their roadmap and this concept future flags. So what's nice is that you can go to GitHub, you can see the rfcs for each feature that are coming up or being proposed, and you can comment on those and you can give your ideas and you can add your own rfcs. And it's really great to have that level of transparency and interest from the creators of the framework. I'll just mention some of the highlights that they have on their roadmap. By the time you're watching this, I think they'll complete the Veet build system. So they have been using a build system based on ES build, which is what Veet uses, but they're actually changing remix so that it's just a plugin within vite and that allows a lot more flexibility and will add a little bit of speed. They're also looking at loader and action middleware, which will allow you to do authentication or other checks before each and every action or loader. And they're going to give some more tools for optimistic updates. As you saw in our app, it was pretty naive and any changes that happen rely on having a loader reload the data when the action is done. So real world APIs can take a little while. And so it's nice to be able to have some tools where you can update the UI before anything actually comes back from the server. And I love this idea of future flags that they've done. So remix recently came into version two. Before that, in version one, your remix combination file looked something like this. There were six breaking changes between V one and V two, and what happened was you could opt into them one at a time. So for example, error boundary syntax changed. And so in your application you could say okay, I'm going to go in and I'm going to edit every single error boundary function, and then I'm going to put this future flag v two error boundary true, and then the application will run normally and we've already done one 6th of the migration, and if you're really on top of it, you can do these migrations as they come out, so that by the time the new version comes out you're already ready for the new APIs and the breaking changes aren't a big deal. And I love that because it's so easy for you to use some framework and then when a new version comes out it seems like so daunting to go back and change everything and deal with all the breaking changes. So this makes it very nice. It allows you to get new features faster and allows you a lot of freedom in terms of when you want to update these breaking changes. So to recap the philosophy and how you might use it in other contexts. So in any application you can directly use fetch and form data and URL, and you can learn those web standards and they're not super hard and they're meant to be really helpful and useful and eliminate the need for a lot of NPM modules that do the same thing. You can even learn about runtimes like Cloudflare workers and Dino and bun, because your request and response objects are basically the same as what you would work with in those runtimes. And then it also is nice to think about what can we do to make the experience good for people on mobile with low cellular speeds, and what can we do for any other kinds of challenges that you might run into, like a user not having javascript at call. And to recap what I love about remix, when I write remix route, I don't have to worry where my code runs. It could be server side, it could be client side, it could be the first render, or it could be that the user has JavaScript disabled. So it's the same kind of code regardless of that, and you don't really have to think about it. In fact, for the first several days that I use remix, I turned the script tags off completely just so I could kind of see and understand how these form mutations work and kind of the different mindset and how it's liberating. Because of these kinds of patterns, I don't have to think about duplicating code on the client or server, I don't have to run into all of the gotchas and pitfalls of server side rendering, and the performance is just great. It's great for first renders, it's great for subsequent renders, and I don't have to think about the different difference between them. Finally, I'll just mention that if you're interested in learning more about line and what we do for cancer patients, our website is at Shoreline Health. And if you have been, thanks for watching, and thanks to comp 42 for the opportunity to speak. Have a great day.
...

Ken Snyder

Principal Engineer @ Shoreline Health

Ken Snyder's LinkedIn account Ken Snyder's twitter account



Awesome tech events for

Priority access to all content

Video hallway track

Community chat

Exclusive promotions and giveaways