Check out a free preview of the full Remix Fundamentals course
The "Mutations Solution" Lesson is part of the full, Remix Fundamentals course featured in this preview video. Here's what you'd learn in this lesson:
Kent walks through the solution to the mutations exercise.
Transcript from the "Mutations Solution" Lesson
[00:00:00]
>> All right, so mutations, we're going to create a post. So the first thing I wanna do is go into this post server to have the create thing, and then we'll go into the new TSX, so we can actually do that creation. So we'll come into our models post.server, and we're going to export a function called createPost.
[00:00:20]
And that takes a title slug and markdown as an object. And I'm gonna just type this as any for now, we'll come back later and make TypeScript happier about this. And we'll return prisma, yeah, thank you copilot, that works. Okay, so we're gonna create a new post with the title slug and markdown.
[00:00:44]
And then in our new.tsx, we're gonna use that as part of our action. So I'm actually gonna jump down to the JSX first, we're gonna update this to a capital form, capital F form and bring that in from remix-run/react. And this is what's going to be responsible for doing the fetch request that posts the form data.
[00:01:07]
So when the form is submitted, it will serialize all of the form elements, the same way that the browser would to submit this form. And I'll actually talk a little bit more about that later. So we're also going to add a method of post, because the default for forms is to have the method be get.
[00:01:28]
Which means it will take the form values and serialize them as a URL query string and do a get to whatever URL that you're posting to or getting to, I suppose. So we want to do a post, and because Remix is a browser emulator, it has the same prompts that a regular form would have for method.
[00:01:50]
Which means also actually that the form has an action prop where you can do whatever API route or whatever you want it to be. That's again right from the web platform, and this will work exactly the same. And the web platform, if you don't have an action, it just says, post this to the current URL.
[00:02:08]
And so we're still just following the conventions of the web platform in this case as well. Okay, so we've got our form, and that's all that we're gonna do in our JSX here. So now we need to implement an action, because if we try to go and create a new post, we're gonna get a 405 method not allowed.
[00:02:29]
That's coming from Remix, and it's telling us, hey, you posted to a route that doesn't have an action, there's no way to handle that method, not allowed. You're doing a post, this route only supports gets, and it sends back HTML when you do that. So sorry, that's not gonna work for us.
[00:02:46]
So let's handle that. We're gonna export a function, that's typically an async function for actions as well called action. We're gonna take the request, and this is gonna be ActionArgs, which we're gonna get from Remix-run/node. That's a type. And with that request, it's gonna have the form body.
[00:03:08]
So actually, let's go, yeah todo, hold on this. And let's just take a look at what the browser or what Remix is going to do for us when we do this create post. It's gonna blow up, but let's see what that request looked like. So just widen this in big in that.
[00:03:26]
So the headers, actually, well, there is one important piece of this header. And that is the content of the request headers, the content type, application/x-www-form-url-encoded. The X prefix is actually fun little story about this, if you submit like a regular form on the web, it's always gonna have this X prefix, that's because it was experimental.
[00:03:53]
And then it shipped that way, and then everybody built around it, and now we can never change it, so welcome to the web. [LAUGH] So it was never actually supposed to be used and then everybody used it. So but this is a URL encoded form, so if we look at the payload, we're gonna see title, slug, and markdown.
[00:04:12]
This is maybe a bad example because I didn't put anything. So this is the title, this is this slug, and markdown array, whatever. Okay, so if we do that again, now we look and see the payload has all of those values. Now, the browser is actually parsed this to make it nice and presentable, but if you look at the source, this is what it actually looks like.
[00:04:36]
So this is what you're like if you had an express server put in place or whatever, and you wanted to look at what the post body was, that's what it would look like. And so yeah, and if we wanna look at the URL encoded and stuff, it's kind of fancy stuff here.
[00:04:50]
But the point is this is form data, and this is the way that the browser does it, so Remix is again just emulating what the browser does. So for us, we want to get the form data out of the request. Well, it turns out that this request object is just a regular fetch request object.
[00:05:09]
And just like it has a JSON or maybe some of you know that a request and response has a text that you can also call, it also has a form data. And that's a sink, just like JSON and text, and now you get a form data object. And if you're like, what the heck is a form data objects?
[00:05:27]
Well, you can go to MDN and learn all about form data. Because this again is Remix, not hiding the platform from you, but exposing the platform to you so that you can build out your application using the web as a foundation. I think this is really great. The better you get at Remix, the better you get at the web, that is one of the things I love about Remix.
[00:05:52]
Okay, great. So now we've got our form data, this is maybe some of you might not like this part so much, because formed data, to get things out of form data, you have to use this .getapi, which is probably not my favorite thing ever. Because what I'd much rather do is actually what copilot wants me to do here and just like distract structure all of those things off form data.
[00:06:15]
Here's the reason why you can't do that. Because in a form, it's totally an acceptable thing to have multiple of the same named elements. Okay, so you can have multiple those, this is good for checkboxes or if you've got like a list of things, and you wanna add arbitrary number of those things.
[00:06:35]
And so for that reason, you can't just have it be an object because there could be multiple of those things. And that's why form data has a getAll, or you can say get me all the titles, and then that will return an array of form data entry values.
[00:06:48]
And we'll talk more about that too. But yeah, this is a key thing that maybe I glossed over a tiny bit, but the name of each input is what the name of the form data that you're gonna retrieve. So we're gonna get the title, the slug, and the markdown.
[00:07:05]
And if I wanted to change this to content, then I would say formData getContent to get that one. Okay, great. So we'll say formData get(*title*), and let's see if copilot can help me with these. Yep, thank you. Thank you very much. Co-pilots kind of useful. Okay, so we get our title, slug, and mark down, now we're gonna create our posts.
[00:07:27]
await createPost, nope, not portal, post. There we go. Title, slug, and markdown, and now we have our new post right there, and we can use that for whatever. But I wanna just redirect to post admin for this, so we'll return a redirect which is gonna come from Remix rendernode just like the JSON helper Ttoo redirect to posts/admin.
[00:07:53]
And that is the finished implementation. So New post, new-post-2, whatever. Yeah, there it is. And then we haven't implemented the slides yet, but it worked. And so if we go to posts, there it is, new post, whatever, yeah. Okay, so it may have happened so fast you didn't notice, or you hadn't thought about this yet, but watch really carefully what happens to this list on the left.
[00:08:28]
So this is Marc Rocks, Marc with a C. Actually, one of my best friends growing up was Marc with a C, so Marc with a K was always weird to me. But yeah, so Marc Rocks, and then we go create post, watch what happens on the left, it adds that post.
[00:08:49]
Where's my code that says when I create a new post item, I should add a new item to the list? There is no code for me that says that, I didn't write any code to do that. But it's doing the right thing, that's what I want it to do because I'm showing all the posts.
[00:09:07]
This is one of the things that makes Remix and mutations so great, this is why you don't have to do manage state in a Remix app. No application state management because Remix is automatically saying you just made a mutation, let me go and revalidate the data for you, that does that just automatically.
[00:09:27]
It's pretty rad, I like that a lot. Okay, so there are a couple of other changes here, so I will breeze through those here, and then we can go through all the questions, so we can get all the questions at once. So Extra Credit, Error handling, we want to handle some errors.
[00:09:45]
So sometimes, actually, well, yeah, we'll do the error handling first. So sometimes the user might submit values for these that are wrong, or maybe somebody hits your, like there, this is an endpoint. So somebody hits it directly with curl with the wrong data, or all sorts of things could go wrong, so we wanna handle those particular cases.
[00:10:09]
Another thing to keep in mind is that this is not a string, this is a form data entry value, or it could be null. And if we dive into what that is, a form data entry value is a file or a string, so users upload files with forms.
[00:10:28]
And so that's why we want to do a little bit of extra checking to make sure that the user is submitted this properly, so that somebody's not just hitting our thing wrong or whatever. In addition, I think it would be nonsensical to try and make a post that has a slug that has no length, or whatever title, or whatever.
[00:10:45]
So let's do a little bit of validation here. And now I'm forgetting specifically how I did this in the final version, but I think it was something like this. We're going to make an errors object that has, yeah, basically this, except I want it to be null. Okay, so it has a property for each one of these form elements that could have an error.
[00:11:06]
So we have a title, if there is no title then we'll say no, so that that counts for null or an empty string, and then we'll just say title is required. We'll do the same thing for our slug and for the markdown. And now we just need to know, do I have any errors, or can I create this post?
[00:11:24]
So I'm gonna say const hasErrors = object.entries(errors), or actually, let's just do values. So if some of them are truthy, you got to spell object correctly, it doesn't work if you don't, it's kind of weird, okay? So if any of those are truthy, so this is the same as doing like.
[00:11:50]
Same thing, it's just more terse. I don't think it's clever, I just think it's JavaScript. So if any of these values in this object is truthy, then we have some errors. So if that's the case, then instead of creating the post and redirecting, we're gonna return some JSON.
[00:12:10]
So let's return JSON with the errors. And we'll get JSON the same place we get redirect just a helper. And actually, this redirect is also just to helper, if we wanted to do this, we could do new Response, Headers, Location' : '/posts/admin. Which one of those do you like best?
[00:12:35]
Yeah, that's why we have to help her. So but that's all it's doing, I just wanna emphasise, it's just a request response, just web fetch stuff, okay? So if we have errors, then we're gonna pass the JSON, I'm actually gonna put that in an object. And now to access that, I'll give you one guess, use action data.
[00:12:56]
And again, we get type of action to get some helpers here. So let's get our action data and I gotta use correct syntax. So action data can either be this errors object or it can be undefined, okay? So if we have any errors, did I give you the, yeah, we're just gonna use this.
[00:13:24]
If we have an errors, then we want to display those. So if actionData.errors.title, then we'll use that actionData.errors.title. Okay, and then we'll do the same thing for our slug, and for our text area for the markdown. So this will be markdown and this will be slug. And there we go.
[00:13:52]
Still, I've managed to avoid use state, and use effect, and all that use nonsense except for use action data. That's not nonsense, that's just magic, it's amazing. And I mean magic in the positive way, where you're like, wow, I like that, not wow, that's complicated. So now we can go and create a new post, and we get all of those.
[00:14:14]
It's great and then we can fix it. So New Post 3. Yeah, sure. Cool. All right.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops