Using Forms in Next.js (Server Actions, Revalidating Data)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Let's take a look at how you  can create forms and handle   data mutations with the Next.js  App Router and Server Actions. Before we get started, let's see what  we'll build. So I have a pretty simple   form where I can of course add some items and  delete them as well, and while it's simple,   we'll talk about some of the common  patterns you'll need like loading states,   and error states, and also making  your forms progressively enhanced. But first, before we get into the App Router,   let's talk about how you might have been building  forms today. So on the left, in my editor here,   I have an example of a form in the Pages Router.  Now, you'll notice we have the familiar form tag   and event handler for onSubmit, an input, and  then some button. And you click to submit this   form it's going to call your event handler  and we have to call a separate API route. So traditionally, you would create an API  Route in your application, you would talk to   the server securely inside of that API route,  since this code and this event handler would   run in the browser, and then you could get  the response back and do something with it. This is how it's worked in the Rages Router  since Next.js was originally created,   but today we're going to talk about how to handle  things a bit differently with the App Router. So I went ahead and created a new  Next.js application to get started   using create-next-app, you can do `npx  create-next-app@latest -e next-forms`,   which is the name of this specific one to  get all the code that I'm showing today. So we have a basic root layout here, and  then the entry point into our application,   which is the page file that marks the  entry route. Now, inside of this file,   this is a server component in the  App Router and it's asynchronous,   so we can fetch data directly to display the  todos on the page, so it's marked as async. We grab the todos back from really anywhere,  in this case we're using Vercel Postgres,   but could be any service or any promise,  and we take those todos and we map over   them inside of a list and then we  also add a form for adding todo. So some basic scaffolding here to get our  application ready. Let's take a look at   adding a todo. Okay, so let's look at the add form  component. Now, there's some things in here that   might look familiar, and also some new things,  so starting on line 25 we have the form tag,   but you'll notice instead of using an event  handler like onSubmit, we have the action prop. Now, for those who are familiar with  HTML, this might look familiar. The   action prop is used for processing a  form, and in traditional applications,   that action is some URL that it sends the form  data to. Now, this is great because it allows   you to really easily send over your data and  it works if you have JavaScript disabled. But   what if you want to have more than one form on a  page? What if you want 10 forms on a page? Well,   the cool thing about server actions is  it's going to make this really easy to do,   plus some other benefits that we're going to  get into. So this action, which is actually   just calling a function that runs on the server,  which means there was no separate API that we   had to set up, there was no API Route or  no Route Handler, we just call functions. We of course have an input, as well  a submit button, and then also a live   area that we're going to talk about  a bit later around accessibility. So,   we have this form action that takes in a server  action called createTodo, and then it also has   some initial state. This initial state is just  a message that we want to return from our server   action whether it was successful or it failed,  but let's jump into this specific create todo. So this file contains all of the server  actions for our applications denoted by   the "use server" directive at the top of the  file. That means that everything in this file   runs securely on the server and does not  get sent to the client. So specifically,   we have our createTodo server action  that takes in the previous state from   our useFormState (we'll get to that in  a second) and then also the form data. Now, let me step through each part of  the server action, how it's happening,   and then I'll show an example of how it works. So first we have the form data. We're using  Zod to validate the data that we're getting   back is correct, so we're expecting a string and  we're expecting it to be there. You could use   whatever library you want you here, but this  is just an example showing how we're parsing   that schema and then getting the data back. Then  inside of a try catch, we call Vercel Postgres,   or we call our Postgres database, to  insert this new value into our TODOs table. Then, we revalidate the data. So this is going  to go to the index of our application it's going   to look at the data that's been cached  when we fetched it back from the server,   it's going to say you know what we just did a  data mutation, so revalidate all of the data on   that path, and then it's going to return back a  message saying that it successfully added todo. Okay, so what does this look like?  Well, let me go over to the browser   on the right and I'll add a new todo that  says new todo, hit enter to submit my form,   and we can see it was successfully  added here. And this works whether   we have JavaScript enabled or disabled, which  can show how progressive enhancement works. So let me just disable JavaScript here and I can say   "another one" and add it and we can see that this works as expected here. Let me   just re-enable this and this works for adding or deleting todos as well too. Now, you might have noticed  that when we submitted the todo,  it put the add button or the submit button  into a loading state as we can see here. Let's take a look at what that  looks like. So back in our add form, we have this submit button component and  it's pretty simple. It reads from a new React hook called useFormStatus, it uses the  pending boolean to determine whether it should   put this button into a disabled visual  state, and that's really all it takes. Now, it's important to denote that there's  been a couple hooks that we've been using, useFormStatus and useFormState, that are  coming from React. These are new hooks that are in react canary, which is a channel  that's made available for frameworks to adopt   and put the latest React features out into the  world that will then come to a stable general   purpose React channel for the rest  of other standalone React users. I'll include a link down in the  description if you want to check out how to use these hooks in standalone  React on the canary channel as well too. It's a pretty similar story for deleting  todos as well too, so if I go back to my page, I iterate over the todos and then I have  a delete form for each of the individual todos that has the ID in the text. So if I  click in here again, it looks pretty similar. We're using form state to take in both the  action that we want as well as the initial state again. This is just some message for  either an error state or a success state. We have some hidden values in our form  that allows us to pass additional data to the form data for our server action. We have  a delete button that looks very similar to before and then we have our live area as well too. So what does this look like? Well, I'll say  another one I'll add it and I can click delete. Now, as I mentioned before, this allows us  to have "n" number of forms on our page. We can have a bunch of different forms and  critically one really cool thing that Next.js and Server Actions provide here is the data that can  be returned from the server actions can not only update the cache data, it can also provide the new UI in one network roundtrip. That's pretty cool. The Next.js App Router architecture considers all of these individual pieces like fetching, caching, and revalidating data, and integrates  them all together. So for example, when we're deleting a todo, just like in  creating, we're able to call revalidatePath to update that cached data and as you see here  we click delete, it makes a network request, but then in that network request in that round  trip we're able to say you know, what we made a mutation on our data and we have some new UI  to show by updating what values were cached. Now you might be thinking, that's great but  what if I need more fine-grained control over revalidating the data in my application,  more than just an entire path? Well, you can also use revalidateTag for  specific data that you've tagged with the cache tag and then if you're not  using the fetch API we are soon releasing a stable version of a way to tag any data as  a function through cache() that you can both set revalidation times as well as cache  tags on, so stay tuned for that as well. Let's also talk about accessibility. So a couple  things I want to point out in this application, one inside of this form, we've ensured  that our inputs are correctly labeled for screen readers. Two, we have this live area  that analyzes changes in our application, so I have voice over on right now on Mac.  Let's say I want to add a new task here [voiceover] "I am currently on the text  field to enter text. To enter text..." [Lee] and I hit enter [voiceover] "added todo hello" [Lee] and we see that it says that I added this todo hello. Now that's being read out in the live area here that's politely not  interrupting my screen reader if something more urgent came in and it's not being shown, so I've  added a little CSS here so it's only for screen readers and you'll notice that, let me just close  this for a second, if I go back to my action the value that is returned from the server action  that is used by useFormState is what is put into that live area that's announced, so either  we've successfully added a todo in the create case or in the delete case or we've failed to  create or delete a todo. So to quickly show what this would look like in an error state, what  I've done is just put a delay of two seconds and   then I've early returned to show that there was  an error state. I could also throw an error here as   well too, so let me just type in test. I'll hit  submit. [voiceover] "you are currently on the – failed to create   todo" [Lee] and we see that it says failed to create todo which is returned from our server action if there was some issue saving to your database, for example. One other small accessibility bit here is that we're using the aria-disabled attribute  on our button rather than disabled because for some screen readers, that will actually hide the  button if disabled is true, so this is a little bit better for the use case of this example. Now  there's a whole bunch more that we can do with forms in Next.js and with Server Actions in react, we can read cookies we can set cookies we can look at headers we can handle optimistic UI, we can  redirect–there's a bunch of stuff but for the sake of the video today, we'll keep it just to  some of the basics. The docs for this are live on nextjs.org, as well as this example, which  I'll link in the description as well too. Let me know if you want to see more videos like  this and stay tuned for the next one. Peace!
Info
Channel: Lee Robinson
Views: 53,265
Rating: undefined out of 5
Keywords:
Id: dDpZfOQBMaU
Channel Id: undefined
Length: 10min 26sec (626 seconds)
Published: Mon Sep 25 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.