使用Multer React.js和Node.js上传图像,无需使用FormData



我正在开发CRUD应用程序,并且useState钩子中已经有mydata。我不想将其更改为仅用于图像上传的表单数据。我想使用图像{imgUrl}的useState挂钩,就像我的其他状态一样

这是我用来上传图片的文件页面。

import { useState, useEffect, useRef } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { useNavigate } from 'react-router-dom'
import { toast } from 'react-toastify'
import { createListing } from '../features/listings/listingSlice'
import Spinner from '../components/Spinner'
function CreateListing() {
// eslint-disable-next-line
const [geolocationEnabled, setGeolocationEnabled] = useState(true)
const { user,isLoading } = useSelector((state) => state.auth)
const dispatch = useDispatch()
const [formData, setFormData] = useState({
type: 'rent',
name: '',
bedrooms: 1,
bathrooms: 1,
parking: false,
furnished: false,
location: '',
offer: false,
regularPrice: 0,
discountedPrice: 0,
imgUrl:'',
latitude: 0,
longitude: 0,
})
const {
type,
name,
bedrooms,
bathrooms,
parking,
furnished,
location,
offer,
regularPrice,
discountedPrice,
imgUrl,
latitude,
longitude,
} = formData
const navigate = useNavigate()
const isMounted = useRef(true)
useEffect(() => {
if (isMounted) {
if(user){
setFormData({ ...formData, userRef: user._id })
}else {
navigate('/sign-in')
}

}
return () => {
isMounted.current = false
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isMounted])
const onSubmit = async (e) => {
try{
const listingData={
...formData,
}


console.log(listingData)
dispatch(createListing(listingData))
toast.success('Listing saved')
navigate(`/profile`)
} catch(error){
toast.error('Could not add new listing')
}


}
const onMutate = (e) => {
let boolean = null
if (e.target.value === 'true') {
boolean = true
}
if (e.target.value === 'false') {
boolean = false
}
// Files
if (e.target.files) {
setFormData((prevState) => ({
...prevState,
imgUrl: e.target.files,
}))
}
// Text/Booleans/Numbers
if (!e.target.files) {
setFormData((prevState) => ({
...prevState,
[e.target.id]: boolean ?? e.target.value,
}))
}
}
if (isLoading) {
return <Spinner />
}

return (
<div className='profile'>
<header>
<p className='pageHeader'>Create a Listing</p>
</header>
<main>
<form onSubmit={onSubmit}>
<label className='formLabel'>Sell / Rent</label>
<div className='formButtons'>
<button
type='button'
className={type === 'sale' ? 'formButtonActive' : 'formButton'}
id='type'
value='sale'
onClick={onMutate}
>
Sell
</button>
<button
type='button'
className={type === 'rent' ? 'formButtonActive' : 'formButton'}
id='type'
value='rent'
onClick={onMutate}
>
Rent
</button>
</div>
<label className='formLabel'>Name</label>
<input
className='formInputName'
type='text'
id='name'
value={name}
onChange={onMutate}
maxLength='32'
minLength='10'
required
/>
<div className='formRooms flex'>
<div>
<label className='formLabel'>Bedrooms</label>
<input
className='formInputSmall'
type='number'
id='bedrooms'
value={bedrooms}
onChange={onMutate}
min='1'
max='50'
required
/>
</div>
<div>
<label className='formLabel'>Bathrooms</label>
<input
className='formInputSmall'
type='number'
id='bathrooms'
value={bathrooms}
onChange={onMutate}
min='1'
max='50'
required
/>
</div>
</div>
<label className='formLabel'>Parking spot</label>
<div className='formButtons'>
<button
className={parking ? 'formButtonActive' : 'formButton'}
type='button'
id='parking'
value={true}
onClick={onMutate}
min='1'
max='50'
>
Yes
</button>
<button
className={
!parking && parking !== null ? 'formButtonActive' : 'formButton'
}
type='button'
id='parking'
value={false}
onClick={onMutate}
>
No
</button>
</div>
<label className='formLabel'>Furnished</label>
<div className='formButtons'>
<button
className={furnished ? 'formButtonActive' : 'formButton'}
type='button'
id='furnished'
value={true}
onClick={onMutate}
>
Yes
</button>
<button
className={
!furnished && furnished !== null
? 'formButtonActive'
: 'formButton'
}
type='button'
id='furnished'
value={false}
onClick={onMutate}
>
No
</button>
</div>
<label className='formLabel'>Address</label>
<textarea
className='formInputAddress'
type='text'
id='location'
value={location}
onChange={onMutate}
required
/>
{!geolocationEnabled && (
<div className='formLatLng flex'>
<div>
<label className='formLabel'>Latitude</label>
<input
className='formInputSmall'
type='number'
id='latitude'
value={latitude}
onChange={onMutate}
required
/>
</div>
<div>
<label className='formLabel'>Longitude</label>
<input
className='formInputSmall'
type='number'
id='longitude'
value={longitude}
onChange={onMutate}
required
/>
</div>
</div>
)}
<label className='formLabel'>Offer</label>
<div className='formButtons'>
<button
className={offer ? 'formButtonActive' : 'formButton'}
type='button'
id='offer'
value={true}
onClick={onMutate}
>
Yes
</button>
<button
className={
!offer && offer !== null ? 'formButtonActive' : 'formButton'
}
type='button'
id='offer'
value={false}
onClick={onMutate}
>
No
</button>
</div>
<label className='formLabel'>Regular Price</label>
<div className='formPriceDiv'>
<input
className='formInputSmall'
type='number'
id='regularPrice'
value={regularPrice}
onChange={onMutate}
min='50'
max='750000000'
required
/>
{type === 'rent' && <p className='formPriceText'>$ / Month</p>}
</div>
{offer && (
<>
<label className='formLabel'>Discounted Price</label>
<input
className='formInputSmall'
type='number'
id='discountedPrice'
value={discountedPrice}
onChange={onMutate}
min='50'
max='750000000'
required={offer}
/>
</>
)}
<label className='formLabel'>Images</label>
<p className='imagesInfo'>
The first image will be the cover (max 6).
</p>
<input
className='formInputFile'
type='file'
id='images'
onChange={onMutate}
max='6'
accept='.jpg,.png,.jpeg'
multiple
required
/>
<button type='submit' className='primaryButton createListingButton'>
Create Listing
</button>
</form>
</main>
</div>
)
}
export default CreateListing

我添加了";多部分/形式数据";在listingServices.js文件的createlisting函数的头中,当我读取它时,它是必需的

// Create new listing
const createListing = async (listingData, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
'content-type': `multipart/form-data`,
},
}
console.log({listingData})
const response = await axios.post(API_URL, listingData, config)
return response.data
}

根据文档,我在listingRoutes.js 中添加了Multer

const express = require('express')
const router = express.Router()
const multer  = require('multer')
const upload = multer({ dest: './uploads/' })
const {createListing, getListings, getListing} = require('../controllers/listingController')


const { protect } = require('../middleware/authMiddleware')
router.get('/', protect, getListings)
router.post('/',upload.single('imgUrl'),protect, createListing)
router.get('/:id',protect, getListing)

module.exports = router

为了让你们了解全貌,这里是我的server.js文件

const express = require('express')
const colors = require('colors')
const dotenv = require('dotenv').config()
const {errorHandler} = require('./middleware/errorMiddleware')
const connectDB = require('./config/db')
const PORT = process.env.PORT || 8000
const cors = require('cors')
// Connect to database
connectDB()
const app = express()
app.use(cors());
app.use(express.json())
app.use(express.urlencoded({extended: false}))
app.get('/', (req,res) =>{
res.json({message: 'Heloo'})
})
// Routes
app.use('/api/users', require('./routes/userRoutes'))
app.use('/api/listings', require('./routes/listingRoutes'))
app.use(errorHandler)
app.listen(PORT, ()=> console.log(`Server started on port ${PORT}`))

这是我的listingController.js 中的createlisting函数

const createListing = asyncHandler(async (req,res) =>{
const { bathrooms,bedrooms,discountedPrice,furnished,imgUrl,latitude,longitude,location,name,offer,
parking,regularPrice,timestamp,type,userRef } = req.body
const user = await User.findById(req.user.id)

if (!user){
res.status(401)
throw new Error('User not found')
}

const listing = await Listing.create({
bathrooms,
bedrooms,
discountedPrice,
furnished,
imgUrl,
latitude,
location,
longitude,
name,
offer,
parking,
regularPrice,
timestamp,
type,
userRef: req.user.id
})

console.log({listing})
console.log(listing)
if (listing) {
res.status(201).json({
_id: listing._id,
name: listing.name,
message: 'House has been aded'
})
} else {
res.status(400)
throw new error('Invalid listing data')
}

})

问题是imgUrl保存为imgUrl:"[对象文件列表]";在mongodb中,上传文件夹中没有保存任何图像。这是我第一次使用Multer,我看到的每个文档/教程都使用表单数据接口

首先请参阅文档

app.post('/profile', upload.single('avatar'), function (req, res, next) {
// req.file is the `avatar` file
// req.body will hold the text fields, if there were any
})

你有

router.post('/',upload.single('imgUrl'),protect, createListing)
...
const { bathrooms,bedrooms,discountedPrice,furnished,imgUrl,latitude,longitude,location,name,offer,
parking,regularPrice,timestamp,type,userRef } = req.body

第二种文件类型看起来像这样(可能您对buffer感兴趣(

interface File {
/** Name of the form field associated with this file. */
fieldname: string;
/** Name of the file on the uploader's computer. */
originalname: string;
/**
* Value of the `Content-Transfer-Encoding` header for this file.
* @deprecated since July 2015
* @see RFC 7578, Section 4.7
*/
encoding: string;
/** Value of the `Content-Type` header for this file. */
mimetype: string;
/** Size of the file in bytes. */
size: number;
/**
* A readable stream of this file. Only available to the `_handleFile`
* callback for custom `StorageEngine`s.
*/
stream: Readable;
/** `DiskStorage` only: Directory to which this file has been uploaded. */
destination: string;
/** `DiskStorage` only: Name of this file within `destination`. */
filename: string;
/** `DiskStorage` only: Full path to the uploaded file. */
path: string;
/** `MemoryStorage` only: A Buffer containing the entire file. */
buffer: Buffer;
}

最新更新