I have a multipart form and I am using formData to send the data with a 'PUT' request. I am getting the error Cast to objectId failed for value 'undefined' (type string) at path '_id' for model 'Blogpost', and when I console.log req.params.id on the controller it returns undefined. I am using react-hook-form, redux-toolkit and multer-cloudinary for the image upload.
My frontend is:
const BlogpostEdit = () => {
const { id } = useParams()
const {
data: blogpostData,
isLoading,
refetch,
error
} = useGetBlogpostDetailsQuery(id)
const [updateBlogpost, { isLoading: loadingUpdate }] =
useUpdateBlogpostMutation()
useEffect(() => {
if (blogpostData) {
setValue('image', blogpostData.image)
setValue('title', blogpostData.title)
setValue('subtitle', blogpostData.subtitle)
setValue('content', blogpostData.content)
setValue('category', blogpostData.category)
}
}, [blogpostData])
const {
register,
setValue,
handleSubmit,
formState: { errors }
} = useForm()
const onFormSubmit = async data => {
if (data.image[0]) {
const formData = new FormData()
formData.append('_id', id)
formData.append('image', data.image[0])
data = { ...data, image: data.image[0].name }
formData.append('image', data.image)
formData.append('title', data.title)
formData.append('subtitle', data.subtitle)
formData.append('content', data.content)
formData.append('category', data.category)
try {
const response = await updateBlogpost(formData).unwrap()
toast.success('Blogpost has been updated')
refetch()
} catch (err) {
toast.error(err?.data?.message || err.error)
}
} else {
try {
const response = await updateBlogpost({ ...data, _id: id }).unwrap()
toast.success('Blogpost has been updated')
refetch()
} catch (err) {
toast.error(err?.data?.message || err.error)
}
}
}
return (
<FormContainer>
{loadingUpdate && <Loader />}
{isLoading ? (
<Loader />
) : error ? (
<p>{error.data.message}</p>
) : (
<>
<img src={blogpostData.image.url} style={{ width: '150px' }} />
<form onSubmit={handleSubmit(onFormSubmit)}>
<label htmlFor='image' name='image'>
image
</label>
<input type='file' {...register('image')} />
<p>{errors.image?.message}</p>
<label htmlFor='title' name='title'>
Title
</label>
<input type='text' {...register('title')} />
<p>{errors.title?.message}</p>
<label htmlFor='subtitle' name='subtitle'>
Subtitle
</label>
<input type='text' {...register('subtitle')} />
<p>{errors.subtitle?.message}</p>
<label htmlFor='content' name='content'>
Content
</label>
<textarea
rows='10'
cols='100'
type='text'
{...register('content')}
/>
<p>{errors.content?.message}</p>
<label htmlFor='category'>Choose a category:</label>
<select name='category' {...register('category')}>
<option value=''></option>
<option value='game'>Game</option>
<option value='tv'>TV</option>
<option value='anime'>Anime</option>
<option value='book'>Book</option>
</select>
<p>{errors.category?.message}</p>
<button type='submit'>Submit</button>
</form>
</>
)}
</FormContainer>
)
}
export default BlogpostEdit
The slice/mutation is:
updateBlogpost: builder.mutation({
query: (data) => ({
url: `${BLOGPOSTS_URL}/${data._id}`,
method: 'PUT',
body: data,
}),
invalidatesTags: ['Blogposts'],
}),
The backend controller:
const updateBlogpost = asyncHandler(async (req, res) => {
const { id } = req.params;
const blogpost = await Blogpost.findByIdAndUpdate(id, { ...req.body });
if (req.file) {
blogpost.image.url = req.file.path;
blogpost.image.filename = req.file.filename;
}
const updatedBlogpost = await blogpost.save();
if (updatedBlogpost) {
res.status(200).json(updatedBlogpost);
} else {
res.status(404);
throw new Error('Resouce not found');
}
});
and the route:
router
.route('/:id')
.put(registered, admin, upload.single('image'), updateBlogpost);
Submitting the form the data looks like this:
and the formData:
this is the error on the console:
and the error from the try/catch:
On the controller I get the req.body, the image successfully uploads but the req.params.id is undefined. If I do not add an image and just edit the title for example, everything works. These errors occur when I want to include an image. I use the exact same code on the 'POST' request and it works just fine. Now I know that I can upload the file first using a different route for the upload and a different route to update the model, but I just can not figure out why these errors are happening.
You are passing a FormData
object to the mutation endpoint, which is not like a regular Javascript object that you can simply destructure properties from. You should use the accessor instead to access specific form data values.
const formData = new FormData();
formData.append("_id", "1234");
console.log(formData._id); // undefined
console.log(formData.get("_id")); // "1234"
Update the endpoint to correctly access the id form data.
updateBlogpost: builder.mutation({
query: (data) => ({
url: `${BLOGPOSTS_URL}/${data.get("_id")}`,
method: 'PUT',
body: data,
}),
invalidatesTags: ['Blogposts'],
}),