I am trying to load images in 'postValues' for a request to the backend, I already tried adding images from insomnia and everything works fine, the configuration of the multer and routes, controllers and publication services are ok, the problem is when I try to send images in a forms From the frontend, I have the following code:
/* <----------------------- MODULOS --------------------------> */
import Creatable, { useCreatable } from 'react-select/creatable';
/* <----------------------- FUNCIONES --------------------------> */
import { useState, useEffect } from "react";
/* <---------------- COMPONENTES MATERIAL UI ------------------> */
import Avatar from '@mui/material/Avatar';
/* <----------------------- ICONOS --------------------------> */
import Button from '@mui/material/Button';
import SendIcon from '@mui/icons-material/Send';
/* <----------------------- SERVICIOS -----------------------> */
import { getHashtags } from "@/services/hashtag.service";
import { createPost } from "@/services/post.service";
/* <----------------------- CONTEXTO -----------------------> */
import { useAuth } from '@/context/AuthContext.jsx';
const CreatePost = () => {
const { user } = useAuth();
const [postValues, setPostValues] = useState({
author: user.id,
title: "",
description: "",
images: [],
hashtags: []
});
const [hashtagValues, setHashtagValues] = useState("");
const getDataHashtags = async () =>{
try {
const response = await getHashtags();
const optionsHashtags = response.data.data.data.map(hashtag => ({
// No se utiliza el id, por como se programo en el backend -> value: hashtag._id,
value: hashtag.nameHashtag,
label: hashtag.nameHashtag
}))
setHashtagValues(optionsHashtags);
} catch (error) {
console.log(error);
}
}
useEffect( () => {
getDataHashtags();
},[])
// Manejo de campos simples
const handleChange = (e) => {
const { name, value } = e.target;
setPostValues( (prevState) => ({
...prevState,
[name]: value
})
)
}
// Manejo de hashtags
const handleHashtagChange = (selectedOptions) => {
const hashtags = selectedOptions ? selectedOptions.map(option => option.value) : [];
setPostValues((prevState) => ({
...prevState,
hashtags
}));
};
const handleImageChange = (e) => {
const files = Array.from(e.target.files);
setPostValues((prevState) => (
{...prevState, images: [...prevState.images, ...files]}
));
}
// Envio de postValues
const onSubmit = async (e) => {
e.preventDefault();
try {
await createPost(postValues);
} catch (error) {
console.log(error);
}
};
return (
<div className='flex flex-row p-4 w-3/4 mx-auto space-x-2'>
<div>
<Avatar/>
</div>
<form className="flex flex-col w-full" encType="multipart/form-data">
<input placeholder='Titulo' name="title" value={postValues.title} onChange={handleChange}
className='bg-gray-100 p-2 mb-4 border-2 rounded-md leading-5 transition duration-150 ease-in-out sm:text-sm sm:leading-5 focus:outline-none focus:border-blue-500 flex w-full'>
</input>
<textarea id="postContent" rows="4" placeholder="Cuentanos más sobre lo que quieres publicar :-)" onChange={handleChange} name="description" value={postValues.description}
className="bg-gray-100 p-2 mb-2 rounded-md px-4 py-2 leading-5 transition duration-150 ease-in-out sm:text-sm sm:leading-5 resize-none focus:outline-none focus:border-blue-500" >
</textarea>
<Creatable isMulti placeholder="Hashtags" onChange={handleHashtagChange}
className="bg-gray-100 mb-2 rounded-md leading-5 transition duration-150 ease-in-out sm:text-sm sm:leading-5 focus:outline-none focus:border-blue-500"
options= {hashtagValues}
>
</Creatable>
<div className="mb-2">
<div className="mb-2">
<input type="file" multiple onChange={handleImageChange} />
</div>
</div>
<div className="grid grid-cols-2 gap-2 mb-4">
{postValues.images.map((image, index) => (
<div key={index} className="relative">
<img src={URL.createObjectURL(image)} alt={`upload-${index}`} className="w-full h-full object-cover rounded-md" />
</div>
))}
</div>
<Button variant="contained" endIcon={<SendIcon />}
className="rounded"
onClick={onSubmit}
>
Publicar
</Button>
</form>
</div>
)
}
export default CreatePost
I use the route router.post("/createPost",[uploadImage.array('images')], postController.createPost);
to send the request. And the multer is:
/* <---------------------- MODULES ---------------------- ----> */
const multer = require('multer');
// storage: Memory storage configuration
const storage = multer.memoryStorage();
// limits: File size limit (5MB)
const limits = { fileSize: 1024 * 1024 * 5 };
// filter: Filter function to allow only image files (PNG, JPEG, JPG)
const filter = (req, file, cb) => {
if(file && (file.mimetype === 'image/png' || file.mimetype === 'image/jpeg' || file.mimetype === 'image/jpg')){
cb(null, true);
}else{
cb(null, false);
}
}
// Multer configuration
const uploadImage = multer({
storage: storage,
fileFilter: filter,
limits
});
module.exports = uploadImage;
I am getting the following error:
AxiosError {
message: 'Request failed with status code 400',
name: 'AxiosError',
code: 'ERR_BAD_REQUEST',
config: {...},
request: XMLHttpRequest,
response: {
config: {...},
data: {
state: 'Error',
message: '"images" is not allowed',
details: {...}
},
headers: AxiosHeaders {
content-length: '68',
content-type: 'application/json; charset=utf-8'
},
request: XMLHttpRequest,
status: 400,
statusText: 'Bad Request',
[[Prototype]]: Object
},
stack: 'AxiosError: Request failed with status code 400\n at settle (webpack-internal:///./node_modules/axios/lib/core/settle.js:24:12)\n at XMLHttpRequest.onloadend (webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:125:66)\n at Axios.request (webpack-internal:///./node_modules/axios/lib/core/Axios.js:54:41)\n at async onSubmit (webpack-internal:///./src/components/CreatePost.jsx:80:13)',
[[Prototype]]: Error
}
The problem seems to be in how I send the image data from the frontend to the backend, since it tells me that images is not of type string, when I print postValues
I know that I send this:
enter image description here
Using the driver from insomnia works correctly enter image description here
You can try sending the values of the publication using a form data and going through the array of images:
const onSubmit = async (e) => {
e.preventDefault();
try {
const formData = new FormData();
formData.append("author", postValues.author);
formData.append("title", postValues.title);
formData.append("description", postValues.description);
postValues.hashtags.forEach((hashtag) => {
formData.append("hashtags[]", hashtag);
});
postValues.images.forEach((image) => {
formData.append("images", image);
});
await createPost(formData);
console.log(formData);
} catch (error) {
console.log(error);
}
};