Inspecting Form Data0:00
Let's pick up where we left off and complete the update puppy implementation. In the previous lesson, we've done just enough wiring to be able to hit this update function in the PuppyController whenever we submit the update puppy form on the front end. So instead of printing just this string up there, it would be nice if we could try to print the form values. So whatever fields we've put in the update puppy, we should try to show them in this dump and die.
So whatever fields we've put in the update Puppy, we should try to show them in this stump and die. So let's try replace the hello from the update method with a call to request()->all() and I will once again go and edit Ellie and let's call it Simon Swiss. And for now, I will not select an image and check this out. Very nice. We successfully passing the fields to the backend, so that seemed to be working quite well. But let's try again and this time we're going to upload an image as well.
Multipart PUT Limitation1:05
But let's try again and this time we're going to upload an image as well. All right, go to edit Simon Swiss. But this time I will pick an image and this time check what happens, huh? We got an empty array. What? What about the fields? The Simon and Swiss fields that we've seen just before? Where did they go? And what happens here might very well have stumped you before. Essentially when you upload a file in the form,
what happens here might very well have stumped you before. Essentially when you upload a file in the form, the content type must be changed to multipart/form-data. Now Inertia does this for you, but there is a catch in the Inertia docs. If I search for multipart, we will see a note on some limitations and you'll find out here that multipart/form-data is actually not quite supported with PUT, PATCH or DELETE HTTP methods.
actually not quite supported with PUT, PATCH, or DELETE HTTP methods. And so the suggestion is to use a POST request instead and then do something that's called method spoofing by passing the method in the body of the form submission, uh, with an _method key. And you can see the code example here, it's using the POST method and then passing _method PUT part of the form body.
Method Spoofing PUT2:12
and then passing put method put part of the form body. So if I go back in my UpdatePuppy or PuppyUpdate component, you might remember uh, that at the top we were using post and I've changed this to put here. And back then I had mentioned that I had skipped a step ahead and this is exactly what we're talking about now. Uh, we are going to go back to using the post method.
and this is exactly what we're talking about now. Uh, we are going to go back to using the post method and we are going to use method spoofing to pass the put method part of the body. So let's go and use post instead and go update our submit action to use post. Now remember our puppies update routes expect a put method. So if we try to submit it with post, I believe that it's going to tell us that it's not accepting post requests.
that it's going to tell us that it's not accepting post requests. And well let's try. So I'm going to edit Simon Swiss and pick the image one more time and let's try to update and exactly like I thought it's going to tell us, hey, there is no POST method for puppy/{puppyId}. There's only PUT and DELETE. And if you look at web.php, it makes sense. The two routes that have the puppy/{puppyId}
And if you look at web.php, it makes sense. The two routes that have the puppies/{puppyId} structure, uh, update and destroy, which is PUT and DELETE. So we most definitely want to go through that route and this is why we need to do the methods spoofing. And so I need to add a bit of data to whatever we are posting to the backend. Now, the POST method doesn't accept data here. It actually automatically submits what's in the form, which will be whatever we maintain in that state object.
It actually automatically submits what's in the form, which will be whatever we maintain in that state object. And so well, I can go and add here inside the form body the _method set to put. And by doing that we should now be able to hit the put endpoint. So let's try again. I'm going to edit Ellie, Simon, Swiss and the image.
I'm going to edit Ellie Simon Swiss and the image. All right. And this time hopefully, and yes it works, I have successfully hit the PUT method route and you can see that we have our data including the uploaded file, uh, with the method PUT. So if you ever tried to update an image with a PUT or PATCH method with Inertia, you probably got stumped by this and I won't blame you.
or patch method with Inertia, you probably got stumped by this and I won't blame you. I myself got stuck for a little while until I worked it out. So this is how you do it. And by the way, if for some reason you did not want to add the method put like this, you can also use the transform function which comes part of the useForm hook. So we can grab transform here and that's going to let you transform the data before you send it.
and that's going to let you transform the data before you send it. So in the submit method, what I can do is before I post the data, I can call the transform function and receive the data in here. What I could do is basically spread our data object, which is going to be these fields. And then after that I can add here the _method sets to put. So if you find this method a little bit more explicit
sets to put. So if you find this method a little bit more explicit and less magic, we clearly say, Hey, we want to transform this to make it a put method. Maybe that way fits better for you. Let's try again and see if it works. Edit, pick an image and it still works. Nice. Okay, so it looks like we are able to pass all the data to the backend with the form.
Plan Update Steps5:41
Nice. Okay, so it looks like we are able to pass all the data to the backend with the form. So let's now take all the steps necessary to update our Puppy records. So we want to validate the data, we probably want to delete the old image and upload the new one and optimize it. If we change the image, then we wanna save the Puppy. So let's go step by step and maybe let's write the steps that we wanna do.
So let's go step by step and maybe let's write the steps that we wanna do. We want to validate the data for sure. Uh, then if there is a new image, delete the old image, optimize and store the new image. Then I think if we've done all this, we want to update the Puppy values or attributes. Yeah, and then well we want to save the updated Puppy up that yes updated Puppy and well,
Validating and Saving6:26
Yeah, and then well we want to save the updated Puppy up that yes updated Puppy and well, because it's Inertia, we finally wanna redirect back with a success message. Alright, this is our plan of attack and let's start with the form of validation and I just had to speak it into existence and um, copilot executed it. So we want to validate the request. And so we want the name fields to be
So we want to validate the request. And so we want the name fields to be required, string, 2, 55 characters max, exact same for the trades. And then the image can be nullable because we don't have to change the image. And if it's an image it can be jpeg, png, et cetera, up to 5 megs. This validation is exactly the same that we've done on the form store or the creation here.
This validation is exactly the same that we've done on the form store or the creation here. There's a bit of duplication, but that's all right. And so for a second, let's go and uh, skip the image steps here and pretend we just have name and trade. We are going to try update the puppy values with the form values. So you can see we received the puppy here. So what we can do is go puppy name
So you can see we received the puppy here. So what we can do is go puppyName equals the request name and we'll do the same for the trait. So because if we below this, we've successfully validated this fields, we can uh, change the values for the name and the trait on the puppy and then go puppy->save(), which is going to persist these changes to the database. Again, if everything goes well and we can keep going, we are going to return back.
Again, if everything goes well and we can keep going, we are going to return back to the same route with Success puppy updated successfully. So at this point we should have a working update functionality, uh, if we ignore the fact that images exist. So we're gonna try update with just the name and trade and see what happens. I will edit Ellie, call it Simon Swiss and try to update and looks like it worked.
I will edit Ellie, call it SimonSwiss and try to update and looks like it worked. And at the same time we have circled back to the previous lesson where we were talking about closing the model when the action has finished. Uh, this time we are not deleting the puppy card, so the puppy dialogue is still here when we update it. And that's why the model, it's still open and we need to do something to close it actively. But if I close the model, we should be able to see
and we need to do something to close it actively. But if I close the model, we should be able to see that this dog is now called Simon Swiss. Uh, so the update has worked and I can hard refresh the page. This is definitely persisted to the database. This is the new name of the puppy. Cool. So let's quickly handle the model closing bit. So here the post action takes some options after the route.
So here the Post action takes some options after the route. So I can go here and pass an options object. And here we can do things like preserveScroll. Actually we should do preserveScroll through and copilot stop running my surprises Inertia form actions have a few nice lifecycle hooks. So if I go on, you can see onBefore, onCancel, onError, onFinish, onStart, et cetera. And I think here we can do onSuccess.
before on cancel, on error, on finish, on start, et cetera. And I think here we can do on success. There might be other cases, but if we succeed, definitely we want to, how did we call it? Open. So we want to set open to false, which means that if we succeed, the state is going to go from open to set open false, which is closed. And the very nice thing is it's going to wait until the success. So it's not going to close too early or too late.
until the success. So it's not going to close too early or too late. I'm going to like we did for the delete, add a sleep of two seconds just to prove that it works like intended. And let's edit one more time back to Simon Ozzy this time, why not? And we should have a two second spinner and then success beauty full. All right, well we halfway there. Uh, the next thing we need to take care of is the images.
Updating Images Safely10:18
All right, well we halfway there. Uh, the next thing we need to take care of is the images. Only whenever a user has actually uploaded a new image. So if they have, we want to delete the old one, we want to optimize and store the new one. And so let's do that. First thing is if there is a new image and only if, so we want to run that extra code. So if request has a file called image.
So if go away, copilot request has a file called image_copilot. I'm going to type this to pretend I still can have a job for a while. Well, I think here we are going to first create the new image and store it and then delete the other one because if we delete the first image and then we can't upload the new one, I don't know we are going.
and then we can't upload the new one, I don't know we are going to create an optimized image like we've done earlier. So optimized. And remember we had created this optimizedWebP image action that takes an image and then gives us a file path. So I will accept this and let me scroll up here just to show you when we did this store, because we are going to do something very similar, uh,
to show you when we did this store, because we are going to do something very similar, uh, up here, we had done exactly the same. So we had created an optimized image and then from there, from the result of optimize, we can get the image path and then we can try to store the image by passing the web piece string that this returns to us. Then if we could not store the image, we return back saying, Hey, we couldn't upload the image.
Then if we could not store the image, we return back saying, Hey, we couldn't upload the image. And finally we grabbed the imageURL from this uploaded image. So we actually are going to do the exact same. So I've written that first line. I will grab all the rest here. So we get the optimizedImage and then we try to store the image and get the URL. So let's go down here. Hopefully you follow along.
and then we try to store the image and get the URL. So let's go down here. Hopefully you follow along. If not, go watch the image optimization lesson again. So here I'm going to paste the exact same code which we are going to be able to use to update the Puppy data. Just like we've done here for the trait, we can do puppyImage, URL equals imageURL, but we only want to do this if, uh, there was an image. So let's do it right here.
but we only want to do this if, uh, there was an image. So let's do it right here. Actually puppy image, URL equals imageURL. And you know what? Arguably, um, we could directly set it to the storage URL path like this. Alright, so we've done the comments backwards, but I think it makes more sense. So this was the optimize and store, and next we need to take care of the delete the old image.
So this was the optimize and store, and next we need to take care of the delete the old image. Again, still only within the, if we have an image, if we didn't upload an image on the form, we don't do any of that. So to delete the old image, we are going to go defensive here. Let's start by checking if we can find that old image in the storage. So oldImagePath equals,
that old image in the storage. So oldImagePath equals, so we want the puppyImageURL, but we want to remove the storage segment from that URL. So we're going to use the str_replace and we are going to replace the /storage/ segment with just an empty string. So basically stripping out this storage bit from the path. And then the string that we pass to this str_replace is going to be this puppyImageURL.
And then the string that we pass to this string replace is going to be this image puppy, URL. So that's giving us the old path. And then we can check if that exists in the storage. So if we do have an old image path and this, uh, old image path exists in our storage disk in this condition, then we go and delete that image. That is looking pretty good to me. Let's try and see if we can actually update a puppy and change, uh, the image attached to it.
Let's try and see if we can actually update a Puppy and change, uh, the image attached to it. Uh, I think it's gonna work. Let's give it a shot. Alright, for the 74th time, let's update, uh, this dog that is called Simon again, uh, because it's going to have a new image. Let's call it pixiePhotogenic. And we are going to select, let's try another image. Yep, that'll do. Here we go. And we are going to update in 3, 2, 1,
Yep, that'll do. Here we go. And we are going to update in 3, 2, 1, click, two seconds, sleep. And ah, so close, but it didn't work. What did I do wrong? Let's check the image path. So it looks like storage/puppies and then LF8P. Let's go in the file system, check our storage/app/public/puppies, and see if we find this LF8P. It doesn't look like it's here. Uh, I worked out what's happening.
It doesn't look like it's here. Uh, I worked out what's happening. You probably worked it out before me. Basically I thought, I thought that I was clever by uploading the image first, optimizing it, making sure that it's there and then deleting the old image. But I think what I'm doing in my code is I upload an image and then proceed to instantly delete that uploaded image. I'm not deleting the old image, I'm deleting the image that I've just uploaded.
I'm not deleting the old image, I'm deleting the image that I've just uploaded. I think that's what's happening. So check it out. Uh, I do the image optimization and I store it, and then I update puppy image URL. This is my new image. And then I think, yeah, that's right. I think the old image is that, but that is now the new image. So old image path is actually the brand's new image path. So I upload it and I kill it instantly, which does not,
So old image path is actually the brand's new image path. So I upload it and I kill it instantly, which does not, uh, really do the trick. So if I wanted to keep things in the order that we have here to upload the image first and then delete the old one, what I could do is move that line a bit higher before I redefine this and up here, let's grab the old image path before we modify puppyImageURL. All right, let's edit pixie one more time.
before we modify puppy image URL. All right, let's edit pixie one more time. I'll keep the same name but upload my image that this time I believe is going to go through. Ready and ta. Not bad. Hey, looks just like me. And as you can see, the shortlist image has also been updated. Let's try to edit one more puppy.
has also been updated. Let's try to edit one more puppy. And this time I'll go back to my go-to image. And this should work super nicely and it does, I can tell you in advance. Woo-hoo. I'm gonna like this. And also I'm gonna like Kenzie, so I don't just love myself here. And well, looks like we have update functionality. Okay, and with that, I am happy to officially call, uh,
Adding Image Preview16:57
And well, looks like we have update functionality. Okay, and with that, I am happy to officially call, uh, application a true CRUD application. We have create puppy, read puppy, update puppy and delete puppy functionality. It all works. We could stop there, but there's one more thing I would like to do and it is to have a little image preview here under the image. So before we change the image, there is the current image,
here under the image. So before we change the image, there is the current image, and then when we select an image, we should see that little image under. So it's a little bit more reassuring that we, I have the image we're thinking about and just have this line of text. So there are libraries that do this really well for you, but we are going to do a, maybe a slightly naive, but just vanilla, simple plain implementation.
but we are going to do a, maybe a slightly naive, but just vanilla, simple plain implementation. We're going to display the old image until it gets replaced. If it does, we're going to display a preview of the new image. Uh, so it's going to just improve our form a little bit. And the bonus is we'll be able to also use the same functionality on our CreatePuppy form, which has the same problem of just showing the file name without a preview.
which has the same problem of just showing the file name without a preview. See you there, my friends, and be good. Until then.
