javascriptreactjshttp-postmulterinsomnia

Error when adding images from a request input


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


Solution

  • 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);
    }
    

    };