Как загрузить файлы во внешний API с помощью использования сервера с NextJS 14

[ВНИМАНИЕ]: Мне необходимо использовать серверную страницу, в противном случае я бы использовал компонент «клиентской стороны» с аксиомами.

Я пытаюсь загрузить файл на внешний экспресс-сервер. В любом случае, при отправке на маршрут API req.files.file пуст, не определен или просто пуст.

Я хотел бы отметить, что файл обнаруживается, когда атрибут действия срабатывает после отправки формы. console.info(avatar) возвращает следующее:

file: File {
  size: 476026,
  type: 'image/jpeg',
  name: 'Delta-Force-Operator-in-a-Chinook_4x-scaled.jpg',
  lastModified: 1716527617781
}

Как бы то ни было, проблемы, с которыми я столкнулся, связаны с сервером Express. Я использую библиотеку express-fileupload, и чтобы получить файлы из предстоящего запроса, мне нужно сделать что-то вроде req.files.file, где file — это имя вашего ввода.

exports.uploadObject = asyncHandler(async (req, res, next) => {
  // Prevent execution of code if there is not file in request
  console.dir(req.files);
  
  if (!req.files || Object.keys(req.files).length === 0 || !req.files.file) {
    return next(new ErrorResponse(`No file uploaded`, 400));
  }

  /*
  YADAYADAYADA
  */
      // Return object
      res.status(201).json({
        success: true,
        data: "success"
      });
    });
  });
});

Проблема

Что бы я ни делал, сервер не получает файл, и это то, что отображается на терминале {}: фактический пустой объект, в котором нет ни поля, ни значений.

Это моя форма на странице сервера:

const upgradeAvatar = async (formData) => {
  'use server'
  const avatar = formData.get('file')

  const rawFormData = {
    userId: auth?.data?._id,
    username: auth?.data?.username,
    userEmail: auth?.data?.email,
    onModel: 'User',
    album: 'profile-avatars',
    file: avatar,
  }
  // Output
  console.info(rawFormData.file);
  // My function to external API
  await uploadFile(rawFormData)
}

<form action = {upgradeAvatar}>
  <label htmlFor = "file" className = "form-label">
    File
  </label>
  <input
    id = "file"
    name = "file"
    type = "file"
    className = "form-control mb-3"
    accept = {`image/*`}
  />
  <FormButtons />
</form>

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
1
0
163
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В конце концов мне не удалось реализовать то, что я хотел, поэтому я решил создать дополнительный файл, в который поместил всю логику формы, а также запрос API.

'use client'
import { fetchurl, getAuthTokenOnServer } from '@/helpers/setTokenOnServer'
import { useState } from 'react'
import { toast } from 'react-toastify'
import Image from 'next/image'
import UseProgress from '@/components/global/useprogress'
import axios from 'axios'

const Form = ({ auth }) => {
  const [coverData, setCoverData] = useState({
    file: null,
    filename: `Choose file`,
    fileurl: `https://static.vecteezy.com/system/resources/previews/005/337/799/original/icon-image-not-found-free-vector.jpg`,
  })

  const { file, filename, fileurl } = coverData

  const [uploadPercentage, setUploadPercentage] = useState(0)
  const [error, setError] = useState(false)
  const [btnText, setBtnTxt] = useState('Submit')

  const upgradeAvatar = async (e) => {
    e.preventDefault()

    setBtnTxt('Submit...')
    const token = await getAuthTokenOnServer()
    const res = await axios.put(
      `http://localhost:5000/api/v1/uploads/uploadobject`,
      {
        userId: auth?.data?._id,
        username: auth?.data?.username,
        userEmail: auth?.data?.email,
        onModel: 'User',
        file: file,
        album: 'profile-covers',
      },
      {
        headers: {
          'Content-Type': 'multipart/form-data',
          Authorization: `Bearer ${token.value}`,
        },
        onUploadProgress: (ProgressEvent) => {
          setUploadPercentage(
            parseInt(
              Math.round(ProgressEvent.loaded * 100) / ProgressEvent.total
            )
          )
          setTimeout(() => setUploadPercentage(0), 10000)
        },
      }
    )
    await fetchurl(`/auth/updateavatar`, 'PUT', 'no-cache', {
      cover: res.data.data._id,
    })
  }

  const resetForm = () => {
    setCoverData({
      file: null,
      filename: `Choose file`,
      fileurl: `https://static.vecteezy.com/system/resources/previews/005/337/799/original/icon-image-not-found-free-vector.jpg`,
    })
  }

  return (
      <form onSubmit = {upgradeAvatar}>
        <label htmlFor = "cover" className = "form-label">
          File
        </label>
        <input
          id = "cover"
          name = "file"
          label = {file}
          onChange = {(e) => {
            const myFile = e.target.files[0]
            let preview = ''
            if (myFile instanceof Blob || myFile instanceof File) {
              preview = URL.createObjectURL(myFile)
            }
            setCoverData({
              file: myFile,
              filename: myFile.name,
              fileurl: preview,
            })
          }}
          type = "file"
          className = "form-control mb-3"
          placeholder = {fileurl}
          accept = {`image/*`}
        />
        <UseProgress percentage = {uploadPercentage} />
        <button type = "submit" className = "btn btn-secondary btn-sm float-start">
          {btnText}
        </button>
        <button
          type = "button"
          className = "btn btn-secondary btn-sm float-end"
          onClick = {resetForm}
        >
          Reset
        </button>
      </form>
  )
}

export default Form

а затем я просто вызвал компонент Form на страницу своего сервера.

import { fetchurl } from '@/helpers/setTokenOnServer'
import Form from './form'

async function getAuthenticatedUser() {
  const res = await fetchurl(`/auth/me`, 'GET', 'no-cache')
  return res
}

const UpdateAvatar = async ({ params, searchParams }) => {
  const auth = await getAuthenticatedUser()

  // Redirect if user is not logged in
  ;(auth?.error?.statusCode === 401 || !auth?.data?.isOnline) &&
    redirect(`/auth/login`)

  return <Form auth = {auth} />
}

export default UpdateAvatar

Другие вопросы по теме