1. 13
    Use Remix Actions to Handle Form Submission POST Requests
    5m 13s

Use Remix Actions to Handle Form Submission POST Requests

Kent C. Dodds
InstructorKent C. Dodds

Share this video with your friends

Send Tweet
Published 3 months ago
Updated 2 weeks ago

Let’s make forms for those nested admin routes. We can accomplish this using Remix’s handy Form component. But, we also need to actually handle the form submission. We’ll do that using a Remix action.

In Remix you perform all your GETs with loaders, but everything else is going to use an action. Our action in this case will take a request, parse the form data, use Prisma to create a new post with the form data, and then redirect the user to the admin index.

Remember to also write a new function in a .server file that handles creating the post in Prisma!

Note: Fetch implementation has changed, to log request payload, you need to use Object.fromEntries(). e.g. console.log(Object.fromEntries(await request.formData()))

Kent C. Dodds: [0:00] Let's make this nested route actually have a form where we can create a new post. What we're going to do is save a little bit of time by pasting in some JSX.

[0:09] I'm pretty sure you don't want me to type all this JSX out. We just have a label with an input for the title, the slug, and the text area for the body, as well as a Create Post submit button, but we are using a couple of things that aren't defined.

[0:25] This input class name is just to save us a little bit of time. I'll put that right up here. We can share those class names across those inputs. Then, this form is a capital f Form, not a lowercase form, and that's going to come from @remix-run/react, and this is going to manage posting this to our action.

[0:42] If we save this, we can actually fill this out. We can say this is My New Post, My New Post, and this is markdown -- hooray -- all that stuff. We hit Create Post. Actually, let's open up the Network tab and see what happens here. Create post. This is going to give us a 405 method not allowed.

[1:04] For this request, we can see the payload has all of the data in it, and we are making a request to our Remix server. Remix is saying, "Hey, listen, you're not handling this particular method," and the method is a POST.

[1:17] That's because we set our form method to be POST. In your route module, you handle GETs with the loader, you handle anything else with an Action. We're going to export const action. This is an async function.

[1:30] This async function takes a request. We're going to make this an ActionFunction, so we get some auto complete on our request. This is going to come from the node adapter. Now, we can get that request.

[1:42] We want to make sure we always return a response from our loaders and actions. We'll go ahead and return a new Response. We're going to do a redirect. We'll say, "Headers and location is posts/admin." When they're all done, we'll send them right back to the index route.

[2:00] Then, of course, we also need to set the status to a 302 so that it's a redirect. Then we'll come over here, we'll click Create Post and we get redirected to posts/admin. That's exactly what we want. I don't like doing this every time I want to do a redirect.

[2:15] We're going to use the handy-dandy redirect utility from remix-run/node. Then we can say, "Return redirect("/posts/admin")." With that, we get the same behavior as before except this time, we don't have to do all that nonsense with a new response.

[2:35] Let's do something with this. Let's console.log(request.formData). To get the formData, this is async, so let's await this and let's see what the formData looks like, in our terminal.

[2:47] Again, Actions run on the server like loaders. Our output will be in our terminal, not in the browser console. If I come over here and create a new post. We say, "New post title," and we say, "New-post is our Slug". "This is great."

[3:03] We come down here and create the post. We are going to get redirected, everything works as expected. Down here, we get fields with title, slug, and markdown. Let's grab all of those from our request.formData object, formData.

[3:18] Then we can say, our title formData.get('title') and our markdown and slug will be the same. Slug and markdown. Then we need to use this to create a new post. We'll await createPost with title, slug, and markdown.

[3:38] Then createPost, make sense to go into our post.server. We'll export a function called createPost, and this is going to accept our post. Then we'll return prisma.post.create({data: post}). We'll make TypeScript happy about this later.

[3:56] We'll save that. Last but not least, let's import that function and give it a shot. Onewheeling rocks, this-is-fun, and Onewheeling is the best. Here we go. We create the post. There it is, Onewheeling rocks.

[4:13] Again, we haven't set up this route here, but if we go to our posts page, we're going to see Onewheeling rocks right there.

[4:19] In review, what we did here is, create this new post form. What we did for that is, we created our form in here. We're using the form component from Remix. We set the method to Post, so that we can serialize all of the form inputs into a Post body, so that when this form is submitted, Remix will perform a fetch request with a method Post.

[4:41] Our Remix server will hand that off to our ActionFunction, where we can use the request object to get the formData, which by the way, this is a web fetch request. FormData, if you want to learn more about that, go to the MDN Docs.

[4:55] You don't need to go to the Remix Docs for that. Then we get the title, slug, and markdown out of that FormData object. We create the post using our createPost utility that we created in here with prisma.post.create, and then we redirect the user back to the posts/admin page.

ed leach
ed leach
~ 2 months ago

I don't see posts/admin/new.tsx in the repo. Only notes/new.tsx

~ 2 months ago

Hi, I just wanted to share a small feedback while trying to run the code locally.

Interestingly, on my computer, 'console.log(await request.formData())' prints 'FormData {}' without any details in the console.

Except that, the code works perfectly; it just does not display the form payload.

Thank you again for this great lesson

Kent C. Dodds
Kent C. Doddsinstructor
~ 2 months ago

Thanks! I recorded this a few days before we made a change to our fetch implementation. Do this instead:

console.log(Object.fromEntries(await request.formData()))