React + Servers = Confusion

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
it's no secret that the server actions roll out with next and react has been confusing at best it's a really cool new pattern for how we update data and do mutations and interactions in react and react server components but it's a lot of different things at once and it almost kind of feels like the use effect equivalent in this new model where it has multiple different behaviors depending on where and how you're consuming it none of which are intuitive initially until you really start to feel and get The Primitives I've been getting a ton of questions about server actions as well as seeing source code that seems to fundamentally misunderstand the way you're supposed to use them and think about them and as a result I made a repo where I showcased a couple different patterns with server actions both using and not using trpc to try and highlight some different ways to use the new server action patterns so if you want to try out these new patterns and also understand how they're supposed to and expected to work this video should be really helpful without further Ado let's dive into this repo I'll leave a link in the description if you want to see it on GitHub let's take a look at these different patterns and why you would or wouldn't want to use any given one of them before people say why not bun I used pnpm because bun had issues with Drizzle and sqlite and I wanted to use sqlite to make it as easy as possible to quickly grab this database that I had inlined in the code base so let's go open this up quick have a quick intro showcasing server actions and rsc's both with and without trpc so this first example is vanilla server components plus server action so there's no trpc in this one it's very simple new post submit new post appears and you can delete things doesn't wipe the content of this which we'll talk about why in a bit this all works and importantly this all works with or without JavaScript so if I disable JavaScript another post and it creates it and funny enough it actually wipes this too the reason it's doing it like that is it has to fully reload the page with whatever response you get from the form post we'll go into the details of all of this in a bit let's enable JavaScript again because I want to go through the source code first and foremost so if we hop in here you'll see I have in app three different folders vanilla action trpc client and RSC trpc action these are the three different examples I wrote so vanilla action this is the new react server component way in here here I have some pretty simple backend code cost post equals a wait db. query. posts. find many this is a SQL call to my database that grabs posts and returns them since I'm using drizzle the response is type safe I know it has an ID a name and a created app you can go into the database definition and see this if you want to don't think it's too important but it's right there if you want to take a look anyways we grab our posts now I want to list them I make a quick posts head H1 tag and then I list the individual posts the important piece here is by listing these posts and giving them their own component I can then Define an action in inside of this component first I need the type for the Post View and I just infer the return type off of the posts call because it's easier to grab it that way and then I use that as the type for post but then in here I have my async function delete post action notice that this doesn't take any inputs the only thing a form action gets is form data which if you want to parse the form data yourself is great but when you click the button once you've bound it to the form you also have to include all that data in the form which is not a great workflow so what I try and do when I need something like the ID because I need the post idid here in order to delete the post rather than try to find some way to pass this into the action I Define the action somewhere where the ID already exists so if I had defined delete post action up in here then I would have had to embed in the form the ID for every one of those buttons for delete and also parse that out in that function and hoped it all worked as expected because you have no type safety or other guarantees whereas here that value gets encapsulated because it's part of the closure where this function is defined because post post exists here post now also exists inside of here and the way that react does this is by embedding that information in the form if I actually go in here and I look at this form with this delete button you'll see that this form has a bunch of additional data in here this weird hidden input field that's encrypted what this is doing is including those values that might not have been included otherwise as well as identifying which action to run these implementation details shouldn't matter too much but the important piece to know is that for data to go from your browser to your server action it has has to be included in the form if you're using it via the traditional actions method I'll show you something you can do slightly differently after but most of the time you're using server actions especially if you're using them in server files and specifically if you're binding them via form action equals or with action equals if you're doing either of these things then the form has to have the content data does not get to this unless it comes from the form so know that going in in this case it gets here because it exists when the action is defined but if I had pulled this out I would have to pass and manage all that form data myself which is obnoxious so I putting it here once the post already exists you can skip a bunch of that and make code that's way simpler and then in here I have the form the button delete post action and I'm done but because this is running without any JavaScript on the client I can't really have JavaScript behaviors so if I want to empty the content of something I would have to do that in javascrip the client component I also have a note in the code here for the action to work it has to be in a form you can't call an action with without a form or JavaScript and I'll show you what I mean by the lad in a bit let's quickly look at create post before we go too much further though in create post I have this create post action function which does take form data because there's an input and I need that data because this component isn't a client component I don't have a u state so the only way I get the input value is if I read it from the form data and since I bound this post action to this form I now have access to anything in here so I said that the name of this is post- name so now in my server action I have to form data doget post name and also cast it as a string myself because we don't know what this form has in it because the relationship between this and this are not deterministic even if they're really close to each other starting to see why this gets a bit confusing and isn't super reliable because once you're collecting the data this way you now have to validate it and check it yourself because it's possible somebody just posts this Endo themselves like through something like I don't know a postman and you have to deal with that you can write your own type definitions to work around some of this but none of it's a guarantee and you absolutely do need to devalidate like I should check the post name is a string before blindly using it here it's meant to be a simple demo and then I make this form and that's it for the simple basic vanilla solution if I wanted that input field to be wiped what I would have to do is move all of this code into a client component and then set up behaviors there in order to do that I'm not going to do that here because I'm lazy but you're starting to see where client comes in when you want client behaviors versus where the server comes in when you're doing these data back and forth things and as much as this is different there is still something magical about awaiting inside your component and using what you get back immediately it's such a nice experience and I'm really happy with the DX here overall but I'm also calling database things in line here all over the place which isn't the best thing because now I have these weird database calls everywhere there's no guarantee for auditing if you want to learn more about the security and safety model here watch my video about react taint where I go in-depth on next verell and react team's recommendations on how to structure your code to keep things safe one of the recommend in that blog post and video is creating an abstraction layer for all of your data access and changes they call it a data access layer and I think it's a really good pattern we'll go into how I do that with react server components and actions in a bit but first I want to show you a hybrid this hybrid solution is interesting because I explicitly don't use server actions in it and I've gotten a lot of users and developers with confusion around how you combine trpc with react server components and server actions and these are the two different ways you can do it this one I went out of my way to do all of the mutations via TI PC the old school way so all the things that you would use actions for are using client code but the data loading I'm still doing on the server side so here posts is await api. post. getet posts and for us trpc fans I can command click and you know where it's going to bring you my post router where I have a create function that validates your input and it runs this mutation I have my delete function that validates your input and then runs this mutation and I have the posts endpoint which takes the context because it needs that for the database then it just does the database call and returns it obviously that's all type safe because prpc I'm using the server caller pattern for the trpc here so app router. create caller this basically just let you call the functions from trpc directly and because it's a server code this works great I also import server only to make sure you never accidentally use this on client now I have this guaranteed layer where as long as I'm accessing through API I'm guaranteed it's only happening on server and it makes code review and guaranteeing that things aren't leaking significantly easier when you have one entry point for all of your data so this entry point defines all the things we need for the page I have the API post that get posts and then I have my post view this is where things start to get interesting you might have noticed this file is shorter which feels great but that's cuz a lot of the logic's been offloaded both the trpc side as well as into separate component files where I have to have two components now because they need JavaScript in order to run so this version of the page will not work unless you have JavaScript running in your client but now you can do a lot of other things you wouldn't have been able to otherwise I don't oh yeah this is the type error I even commented I'm too lazy to fix the types on this so in here I have my create post function has the use router it has the use State for the current name and then I have my API call api. post. create. use mutation and this is the use mutation from trpc and react query that lets me make the actual post so on here I have my onsubmit I prevent the default and then I create post. mutate with the current name pretty traditional react code if you ever done this you could also put a separate button and not even have this be a form but it being a form is a little more correct in here I have my input I set the value doesn't notice this a string and I don't feel like setting the types correct so it does so it savs as it is I have the button type submit which triggers the submission this all roughly what you expect the important piece here is the router. refresh which says hey by the way I want new page content after this is done but this is an additional waterfall so the mutation happens it responds to the device and says hey user the mutation's complete and then that device has to then go run the router refresh also set the state to be empty string again but it then goes and gets the new data and reloads the page content without fully reloading the page this is how you refresh the content that came from the server comp components so if the content of your server components has changed like in here the posts are different now we refresh in order to get the new posts and that does waterfall there's a round trip here now versus with the original version where in the vanilla action I actually call revalidate Path and what this says is hey next I need to during this mutation during this action regenerate the page content and send any changes back to the user so in just that one post request you get the new content without having to Waterfall really cool stuff versus here you have to do the waterfall which to be fair this is how almost all applications work today they do the mutation they're told it's succeeded and they go back and do another call to get the updated content pretty common alongside things like polling so fine but having it all in one request response especially one that doesn't need JavaScript that's some of the coolness of this new pattern and then deletes nice and simple same deal where I have to manually router. refresh when the content is changed which is a little annoying because it makes that delete button feel much worse and I'm not going to do any artificial slowdown I'm just going to hit delete here it was almost immediate with the vanilla action version so if we go back to the trpc client side version and I click a delete you'll see it takes a good bit longer and you feel the difference you really do because it has to do the delete get the response back from the server saying the delete was done successfully and then ask the server for the new content and just doubling the number of trips makes the performance feel much worse creating a post feels not quite as much worse especially since the text field resets which it doesn't in the other version again you can Implement that yourself but let's do a quick check testing the title huh what's happening check console error viewer is not subscribed you're not subscribed I'm providing you all of this value for free you can't bother hitting the sub button you know subscriptions are free right just click that little button at the bottom of the video helps us out a ton thank you let's go delete that code so I can actually show you the creation of a post which feels pretty good overall but since I failed in the onsuccess we ended up with two of the same post anyways let's take a look at the third option the hybrid because I think the hybrid hbd is really interesting so again with this one I am using trpc the first thing you're going to notice I don't have that components file anymore because I don't need use client in order to do the posting because I'm using server actions in here you'll also notice the code looks very similar to the vanilla version at least this far and once we get into here you'll notice it looks nearly identical same infer async return type here same post view code here the difference is the server action calls api. post. delete with the post ID and by doing this we get the benefit of the data access layer and we're still using trpc but we have all of the progressive enhancement benefits and most of the DX benefits of using server actions directly but if you're using a server action with trpc you really should make sure you're calling trpc via the caller directly which is what I did here with create caller and that you're not using an HTTP based calling method or a proxy calling method which is actually what we're defaulting to in creat T3 app right now for app router and we absolutely shouldn't be in a complain to the team a lot we're going to get that fixed the catch with caller is that you can't do an async context since DB is sync and headers is sync this is fine but if I had an off function that was async here this wouldn't work sadly that's the only catch otherwise this pattern works perfectly fine with trpc as it currently stands I think this hybrid pattern is a really really strong way of working with the new server action stuff but I wanted to show one more piece I said there was four mutations that's the name of this repo but I actually only have three made right now so I'm going to show you guys something I think is really cool we're going to clone the trpc client I'm going to rename this to client side actions because server actions have a really interesting hidden Behavior it's not that hidden but it is very interesting right now in components we have create post which uses the use mutation from trpc which means we have to use the react query we have to do all of this here what if I told you we can still do this on client side with client side code while getting most of the benefits of server ACC actions and that it's actually one of the default behaviors of server actions we're going to make a new file here called actions. TS very importantly we're going to Mark the file as use server and I'll be very clear about something there's a lot of confusion about this you should never have to mark use server on a file that exports components only reason you mark a file use server is you're exporting actions for other files specifically client components to use so what I Define in here needs to be an exported async function this might not error here but if I go into here oh they stop giving you an error for that previously if you exported anything from a used server file and it wasn't an async function they would yell at you for it I think that still probably should be the behavior because this function should not be accessible to other things otherwise but if I go put this in create post maybe look at the error now console.log aha there's the error I'm looking for they were treaking it out once you actually try to use a value from a use server file you will get an error because the only only thing you're supposed to export from a Ed server file is an async function and they call this out specifically because of the thing that we're about to do so let's delete that all because we don't actually want to export or import a string what we want to import is a function with specific behaviors so if I go back to the RSC trpc action hybrid and I grab the create post function I have here we're going to do things a little different though because as mentioned before I hate this form data what if instead of form data we just had input name name String and I can just say input. name here and I have to import API and I have to import revalidate path looks pretty simple right don't even need the use server there anymore so I have this asnc function but I can't just bind this the way I did before CU we're in client code I can't just bind that to the form how do I use this well I'll show you we're going to hop down here and we're going to call create post action with name okay I can't wait in that let's see what this is mad about promises must be awaited end with a call to catch or then cool this our es lint rules really nice so that then we want to router. reload and we want to set name to empty string I have to void to let it know I don't care about the result cool so now in here I'm calling the post action how can I do that though this is client JavaScript and I'm not binding this as an action well this is that hidden Behavior I was mentioning previously oh yeah the uh I don't even need to do the refresh anymore you're right yeah so I need to change this to be the right URL really good points guys thank you client side actions so what this says here is when this function is hit the response should include the content of this page so if you're on it we can get that new content and now when I go into the code here I have the create post action which is just a traditional promise the way you would call otherwise and This Promise gets past name from my react code and then it sets name to empty string when it's done let's see if this works oh create post. loading that's not a thing anymore so I don't have a lot of these other states you can do it you can get those with used form status I'm too lazy to do that now let's do a quick client test and that was almost immediate the way that this works now is when you import a server action this becomes a fetch call this becomes a built-in function that's almost like an RPC where as we Define this on the server and it's accessed on the route this becomes a post end point that the client can hit and when you call it the compiler turns this into a fetch call and handles it behind the scenes for you this is dope because you can write a server action and bind it to a form the way I showed earlier or you can throw it in a file marked you server and import it and basically have a DIY trpc I liked this import pattern so much initially that I actually made a library discussing how to use it all the way back more than 8 months ago Zod server actions the goal here was you define a server action you export the validated action which is a wrapper around a server action definition you validate it with a Zod validator and then you pass it racing function and now you can just call this on client I even wrote a basic uact hook which was kind of like use mutation for react query a mutate data and is running and now you have all of that state super simply in line that said I don't want to maintain this somebody else made next safe action which is a very similar library that does the same thing but is way better maintained and probably works as expected our library was a total Jank mess so I'm really hyped that somebody else made this for me and I can recommend it to you you define a schema you pass it the schema and your acing function everything behaves as you would expect it to and it's very easy to introduce the use action call that they provide to execute these things in line it's a really cool pattern but again it's an optional pattern that you can build on top of this really strong primitive so this is the the thing I said at the beginning was a little weird and a little use Effy you server does something different depending on if you do it in a file and Export a function versus if you do it in a server component and bind it as an action these are different behaviors that use the same terminology because they're roughly achieving the same thing when you define it in the component itself on the server and then bind it via the action call like we do here these are very different behaviors but they both let us do roughly the same thing and this is the thing I wanted to highlight server actions can be used in these two different ways as a serers side binding to a form so no JavaScript is necessary or as an asynchronous import that does a fetch call inside of your client code so if you really want to control your server actions on the client have loading states have these different behaviors this is the place to do it and if you don't you can bind it with full Progressive enhancement all these fun new features by just writing it in the server component directly I wanted to highlight all these different options with these new patterns because I think they're all pretty cool in the resulting DX is immaculate I've been loving all of these patterns in mixing and matching for what makes the most sense for what I'm building when I'm building it I haven't settled on the the main path I'm going to take but I definitely know a data access layer will help us as we continue to grow our code bases with app router and I think I'm probably going to use trpc for that data access layer that's all I have with this stuff again the repay will be in the description might even push the changes I just made up now so you can see all of these different ways of building using the new server actions patterns I hope this helps clean up some of the confusion and know we're not talking about the SQL templating thing you should check out Josh's video for that if you want to learn more about doing server components in app router safely I'll put a video in the corner all about react taint if you've already seen that or you want something else YouTube thinks you like the video below it appreciate you guys as always peace NS
Info
Channel: Theo - t3․gg
Views: 40,854
Rating: undefined out of 5
Keywords: web development, full stack, typescript, javascript, react, programming, programmer, theo, t3 stack, t3, t3.gg, t3dotgg
Id: cY2SpxhEdyE
Channel Id: undefined
Length: 20min 29sec (1229 seconds)
Published: Fri Nov 17 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.