Transcript for:
Next.js Server Actions: Challenges and Solutions

nextjs server actions are stable but I still have one big issue and no it's not a concern about mixing front-end and backend code in one place I'm actually okay with that I actually like it don't add me it's the fact that this code doesn't work and it took me hours to figure out why so let me show you how to fix it and at the end of this I'll show you a few extra gaches that I learned while using server actions in my latest project before I can show you my specific problem with server actions let's start with a brief overvie of what server actions are server actions are a way to handle form submissions in nextjs without having to create separate API endpoints like you used to have to do in the past this basically means that you can combine your serers side logic with your react components and a lot of people have a big issue with that this image actually went viral recently showing an inline form submission Handler that exclusively runs on the server directly inside of the markup for a react component so let's address that directly really quickly to create a server action you can to find it right inside of your react component but you don't have to so if you're using server components you can add the function directly in line or at the top of the function component if you are using a separate file you can import this into a client component or or a server component so this gives you flexibility to break this out into its own directory of actions and then import them and use them where you need them now this would be my recommended way to go because it gives you a little bit of separation between the backend code and the front end which is something that makes a lot of people uncomfortable so if we look down here in the documentation for a little bit more of the basics you basically Define a function inside of your component or elsewhere and then you annotate it with this use server directive now this is kind of inconvenient and a little grossl looking but at least it explicitly tells us where this code is going to run now if you're wanted to use these with a client component you can Define your action inside of actions file that has the U server annotation and then import that into a component that uses the use client annotation again a little gross but also very explicit on where the different pieces of code are going to run now you can even pass in a server component as a property to a client component and be able to reference it that way so there are a few different options to set this up so let's now start with my specific use case I've been working on a platform called Deals forevs Deals Ford dev.com quick shout out to both century and Zeta for sponsoring my time to work on this project it's a little late now but it was basically me trying to aggregate a list of deals for developer related things for video platforms tools Etc and I had a lot of fun building this and experimenting with nextjs now now I wanted to allow people to submit deals so I have a form that they can fill out which will submit a deal that's not approved yet behind the scenes this will save this to a table in a Zeta database which is where all the data for this application is saved then I have an admin dashboard where I can go in and approve that thing so that it actually shows up on the site in this case I'll reject this because that's not a real piece of data now one thing that I started to realize is that I was getting duplicate submissions like this now usually this is a red flag that something is wrong with my code this means that someone is clicking the submit button not getting feedback back on that submit actually being clicked and then clicking that thing again before the actual submission has finished taking place this is obviously a very poor user experience it also means more admin work for me to go and find the duplicates and remove them now what's interesting is that was just my thought of what was happening but I was actually able able to confirm this inside of the Sentry dashboard now I integrated Sentry into my project to be able to track errors have alerting Etc and when I went into the replay section I saw that they have a dead clicks section on here and dead clicks are defined by a user clicking a button and nothing happening for 7 seconds so basically what would happen is users could click my button and I was showing no feedback that it was actually trying to submit and most importantly I didn't disable the submit button so this was considered a dead click and I was able to find this out inside of the Sentry dashboard and see that this has happened 26 times it even gives me the tail one classes that are on this element which means I could go back and directly associate this with a button that is inside of my form to be able to submit these deals so I went back to the documentation for nextjs server actions to try to find a solution to this now on the forms and mutations documentation there is a displaying loading State section that shows exactly how to do this it seems very simple basically you convert your form to a client component you then use the use form status hook and grab the pending property and then you can use the pending property to be able to disable the button update the text on the button Etc so that's exactly what I did so I started out with a regular server component that looks like this has a bunch of form stuff in here at the end I have a button for my submit so then I updated my code to add that pending hook and then reference that inside of my button so notice that I turned this into a client component I then used the hook just like the documentation said I grabbed the pending property I did some debugging to see when that thing was changing because it wasn't doing what I thought and then if we scroll down I update the text on the submit button to show the dot dot dot while it's pending and then disable this thing while it's pending as well now I'll be honest I did the wrong thing and I just pushed this code to to production without actually testing it because I assumed it would just work as is it seems pretty simple right well I realized that that actually wasn't happening correctly at all so if we go back to the locally running site and just try to add in some dummy data here to test this submit we can see that there's no loading State down here now if that was too quick we could also go in and debug this a little bit further to throttle the network and make it on a slow connection which should give it more time to be able to show this so under the networking tab we can enable presets for fast G slow G so I did that and then I went through the same process again add in dummy data and submit but the same thing is happening no feedback no idea that this thing is actually submitting the way I wanted it to so this is where I spent hours now trying to debug why was this not handling this pending State the way it was supposed to again the code is simple enough we convert this to a client component we use the hook we grab the property and then we use it to update the text and then disable the button eventually it took going back to the documentation and reading in detail every sing single word in here and I got to a line that says that this hook can only be used as a child of a form element using a server action now in my head I was kind of associating this with the child of a form meaning the button triggering the form submission so the button would be a child of the form not a child component but inside of the same component just be able to reference a button inside of the form and then use the hook however that's not the case I don't have any idea why it has to be this way but that logic has to be broken out into its own component that is a client component now inside of your form you can then reference the submit button and keep the form as a server component but import that submit button and it can handle all that logic itself so this is what I ended up doing I ended up getting rid of the used client annotation inside of this form which keeps it as a server component I then remove the use effect and remove the use form status hook so I'm not referencing any of that stuff inside of my form then I import my submit button that has all that logic so I basically just copied over all the used client the Imports the pending State and then my button as well including the logic to handle the pending State inside of here now this is the only thing I had to change but now when I go back and try to submit this form you can see that I actually have the loading State and it handles the submission exactly like it's supposed to while also giving the user feedback that something is happening and disabling that button so I'm no longer going to get any duplicate entries into my dashboard but the fact that we have to move that logic into its own client component still seems a little weird to me and I don't really understand the requir I'm sure there's a good reason for this and I understand that some things just have to happen but it just seems weird so all in all I was able to figure it out but I think this is part of the adjustment period that it's going to take to be able to adopt something that's brand new like server actions in nextjs so if you're looking to get started here are a couple of the other things that I learn that might help you along the way the first thing that I would recommend is to go ahead and create a separate actions directory to host all of your actions now this gives you a clean separation of this code which runs on the server with your server components or client components additionally one thing to know is that you can't import things into a client component from a file annotated with us server for example originally I was exporting this category enum from this actions file the problem is if I wanted to go and reference that inside of a client component I would get an error so if I try to get an import for category and from that actions file I'm actually going to get an error inside of nextjs that's really vague and not very helpful but it's basically coming down to you can import things like that from a server file into a client component so if you need to be able to access something like this from the client go ahead and create a separate types directory and file to be able to save this in so you can import this in your server side code and your client side code and not have any issues now the other thing I think is really neat is the ability to Define your schema with Zod and be able to validate your data on the server and then respond back with some sort of answer the thing that's important though is that you try to parse the data coming from the form into that schema and then catch those air SS from there you'll want to be more specific to return some sort of data back to the front end that shows the air and gives feedback to the users on what's wrong I definitely have a lot of work to go from here but this is just something I put together in a couple days now the other thing I ended up doing is after being able to successfully save the data to the database I just redirected the user to a new page now this was easy enough for me to just create this sample page and then redirect the user over since I have the add deal button inside of the nav bar they have an easy way to get back to this form as well so this is seems like a pretty clean flow of being able to submit the form when it's successful navigate somewhere else and they can come back to add another one if they need to so that was my big frustration with nextjs server actions all in all for the most part I've really enjoyed it so hopefully I continue to work with it continue enjoy it and hopefully you do as well now because of all this building I'm doing I'm actually going to build a course around all the things that I've learned to build something similar to what you saw in this video talk about nextjs 14 server components server actions all the things that you can imagine including integrating with a database and Zeta authentication with Clerk and then getting into some of the error tracking and replaying that you saw with centry earlier so if you're interested in that make sure to check out my newsletter at james.com newsletter thanks as always for checking out the video and I'll catch you next time