Multipart form data file upload error: TypeError: Cannot read properties of undefined (reading '0')

Viewed 43

I'm trying to post multipart/form-data using axios. I can send the post request using Postman/Insomnia successfully but it is not working from frontend.

It gives an error in my backend:

node:events:505
      throw er; // Unhandled 'error' event
      ^

TypeError: Cannot read properties of undefined (reading '0')

Also in browser console it shows:

Error: Network Error
    at createError (createError.js:16:1)
    at XMLHttpRequest.handleError (xhr.js:117:1)
POST http://localhost:8080/audio?= net::ERR_CONNECTION_REFUSED

Here is my frontend code in reactjs. Can anyone help me please?

import { React, useState, useEffect } from 'react';
import AppBar from '@mui/material/AppBar';
import axios from 'axios';
import { useNavigate,useLocation} from "react-router-dom";
import { toast } from 'react-toastify';
import Toolbar from '@mui/material/Toolbar';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import ArrowBack from '@mui/icons-material/ArrowBack';
import { Box, Button, Container, Link, Grid, TextField, Typography } from '@mui/material';

 
const theme = createTheme();

export default function AddNewAudio() {

  let navigate = useNavigate();
  let location = useLocation();

  const [title, setTitle] = useState("");
  const [artistName, setartistName] = useState("");
  const [owner, setOwner] = useState("");
  const [thumbnail, setThumbnail] = useState("");
  const [audioFile, setAudioFile] = useState("");
  const [isLoading, setIsLoading] = useState(false);


  const HandleChange = (event) => {
     event.preventDefault();
     setTitle(document.getElementById('title').value)
     setartistName(document.getElementById('artistName').value)
     setOwner(document.getElementById('owner').value)

}

const HandleThumbnail = (event) => {
    setThumbnail([event.target.files[0]]);
    setIsLoading(true);

}
const HandleAudioFile = (event) => {
  setAudioFile([event.target.files[0]]);
  setIsLoading(true);
}

const FileUploadHandler = (event) => {
  event.preventDefault();
  let form = new FormData();
  form.append('title', {title});
  form.append('artistName', {artistName});
  form.append('owner', {owner});
  form.append('thumbnail', {thumbnail});
  form.append('audioFile', {audioFile});

  const config = {  
  method: 'post',
  url: 'http://localhost:8080/audio',   
  params: {'': ''},
  data: '[form]'
  }
 
  const promise = axios(config)
  .then((response) => {
          
    if(response.status == 200){
      navigate('/audiolisting',{state:{token:"Token"} });
      setIsLoading(false);
    }
  })
  .catch(function (error) {
    console.log(error);
    setIsLoading(false);
    if(error.status == 413){
      toast.error("Your File greater than maximum Uploading Limit");
    }
    else{
      toast.error("Error");
    }
  });
  toast.promise(
    promise,
    {
      pending: {
        render(){
          return "Loading..."
        },
        icon: false,
      },
      success: {
        render(){
          return `Added successfully!`
        },
        icon: "",
      }
    }
  );
  
   console.log("title", {title});
   console.log("artistName", {artistName});
   console.log("owner", {owner});
   console.log("thumbnail", {thumbnail});
   console.log("audioFile", {audioFile});

  
}
  return (
    <ThemeProvider theme={theme}>
      <AppBar position="relative">
        <Toolbar>
          {/* <CameraIcon sx={{ mr: 2 }} /> */}
          <Typography variant="h6" color="inherit" noWrap>
          Planet Q Production
          </Typography>
        </Toolbar>
      </AppBar>
      <main>
     
        <Box
        style={{background:"white", margin:"2%"}}
          sx={{
            bgcolor: 'background.paper',
            pt: 8,
            pb: 6,
          }}
        >

          <Container >
          <Grid container spacing={2}>
            <Grid item xs={8}>
            
            </Grid>
            <Grid item xs={4}>
            <Button variant="outlined"  startIcon={<ArrowBack />} style={{float:"right", marginBottom:"-65px"}} onClick={backToListing}>
                        Back to listing
            </Button>
               </Grid>
            </Grid>  

            <div>
                <form encType="multipart/form">
                    <input 
                        type="text" 
                        name="title" 
                        id="title" 
                        placeholder="Song title" 
                        onChange={HandleChange}
                    />
                    <br/>
                    <input 
                        type="text" 
                        name="artistName" 
                        id="artistName" 
                        placeholder="Artist Name" 
                        onChange={HandleChange}
                    />
                    <br/>
                    <input 
                        type="text" 
                        name="owner" 
                        id="owner" 
                        placeholder="Owner" 
                        onChange={HandleChange}
                    />
                    <br/>
                    <input 
                        type="file" 
                        name="thumbnail" 
                        id="thumbnail" 
                        placeholder="Upload your file" 
                        onChange={HandleThumbnail}
                    />
                    <br/>
                    <input 
                        type="file" 
                        name="audioFile" 
                        id="audioFile" 
                        placeholder="Upload your file" 
                        onChange={HandleAudioFile}
                    />
                    <br/>
                    <button 
                        type="submit" 
                        onClick={FileUploadHandler}
                    >Submit</button>
                </form>
            </div>

          </Container>
        </Box>

      </main>

    </ThemeProvider>
  );
}
This is the backend code:

const express = require("express");
const router = express.Router();
const Audio = require('../models/Audio')
const multer = require('multer')
const baseUrl = "http://localhost:8080/audio/";
const mongoose = require('mongoose');

const storage = multer.diskStorage({
  filename: function (req, file, cb) {
    console.log('filename')
    cb(null, file.originalname)
  },
  destination: function (req, file, cb) {
    console.log('storage')
    cb(null, __basedir + "/uploads/audio/");
  },
})
const upload = multer({ storage })


router.get('/', async (req, res) => {


  const audios = await Audio.find();
  res.status(200).send({
    success: true,
    data: audios,
    message: 'Audio retrived successfully'
  })
});


const CounterSchema={
  id: { type: String }, seq: {type: Number}
}
const countermodel = module.exports = mongoose.model('counter', CounterSchema);


router.post('/', upload.fields([{ name: 'thumbnail' }, { name: 'audioFile' },]), async (req, res) => {


  countermodel.findOneAndUpdate(
    { id: "autoval" },
    { "$inc": { "seq": 1 } },
    { new: true }, (err, cd) => {
      if (cd == null) {
        const newval = new countermodel({ id: "autoval", seq: 1 })
        newval.save()
        seqId = 1
      } else {
        seqId = cd.seq
      }
      const audio = new Audio({
        id: seqId,
        title: req.body.title,
        artistName: req.body.artistName,
        owner: req.body.owner,
        thumbnail: baseUrl + req.files['thumbnail'][0].filename,
        audioFile: baseUrl + req.files['audioFile'][0].filename
      });
      try {
        const savedAudio = audio.save();
        res.json(audio);
      } catch (err) {
        res.json({ message: err });
      }
    })
});

module.exports = router;

The client code generated by Insomnia shows that it needs to be the full path of the file. But after some research I found out it is not possible to get full path of the selected files. (How to get absolute path of image in react `<input ="file" />` and store it in local state?)

import axios from "axios";

const form = new FormData();
form.append("title", "Test Song");
form.append("artistName", "Test Artist");
form.append("owner", "Test Owner");
form.append("thumbnail", "/home/rafin/Desktop/test.jpg");
form.append("audioFile", "/home/rafin/Downloads/audio/new.mp3");

const options = {
  method: 'POST',
  url: 'http://localhost:8080/audio',
  params: {'': ''},
  headers: {'Content-Type': 'multipart/form-data; boundary=---011000010111000001101001'},
  data: '[form]'
};

axios.request(options).then(function (response) {
  console.log(response.data);
}).catch(function (error) {
  console.error(error);
});

2 Answers

You should change the fileUploadHnadler Funtion as below

const FileUploadHandler = (event) => {
event.preventDefault();
  let form = new FormData();
  form.append('title', title);
  form.append('artistName', artistName);
  form.append('owner', owner);
  form.append('thumbnail', thumbnail);
  form.append('audioFile', audioFile);

  const config = {  
  method: 'post',
  url: 'http://localhost:8080/audio',   
  data: form
  }
  ...
}

First, undefined (reading '0') means that when accessing an attribute with a key of 0, the attribute being accessed is undefined, which is a classic problem. When we use a.b.c (a['b']['c'] is same, and when b or c is type Number, a[b][c]) to access a property of an object (corresponding to a child object), we have to make sure that b is not undefined.

Then we look at where the error occurred. You are not using SSR here, and the error occurs on the back end. So in most cases, the bug comes from the back-end code.

Finally, there is one fact that we need to be aware of - JS arrays are implemented as objects. And the indexer of a JS array is actually the indexer of a JS object.

Then it's easy for us to find out where it happened: req.files['thumbnail'][0] or req.files['audioFile'][0]. It would be better make a judgment before them. such as if(req.files['thumbnail']) or req.files['thumbnail'] ? `${baseUrl}${req.files['thumbnail'][0].filename}` : null

The reason why there is so much superfluous detail is that this way of thinking will not only solve the problem itself, but also solve similar problems in the future. Or maybe my fetish.

Related