#node.js #reactjs #express #multer #formik
Вопрос:
Я создал приложение с помощью MERN. Теперь я пытаюсь загрузить изображение с помощью Мультера и Формика, но req.file
оно возвращается undefined
, и я не могу понять, почему. Я новичок в этом, но я предполагаю, что это может привести к JSON.stringify
(http.hook) или content-type: application/json
. Я также пытался сделать это с FormData
помощью , но это не работает. Есть какие-нибудь идеи?
ОБНОВЛЕНИЕ: С почтальоном работает хорошо. Я думаю, что проблема в части пользовательского интерфейса, ввод не передает файл.
app.js
const {Router} = require('express');
const multer = require('multer');
const auth = require('../middleware/auth.middleware');
const Users= require('../models/Users');
const router = Router();
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './client/public/uploads/');
},
filename: function (req, file, cb) {
cb(null, file.originalname);
},
});
const upload = multer({
storage: storage,
limits: { fileSize: 10 * 1024 * 1024 }
});
router.post('/create', upload.single('image'), auth, async (req, res) => {
console.log(req.file);
try {
const code = req.body.code;
const existing = await Users.findOne({code: code});
if(existing) {
return res.json({user: existing})
}
const user = new Users(req.body);
await user .save();
res.status(201).json(user);
} catch (e) {
console.log(e);
res.status(500).json({ message: 'Error: try again.' })
}
});
http.hook.js
import {useState, useCallback} from 'react';
export const useHttp = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const request = useCallback(async (url, method = 'GET', body = null, headers = {}) => {
setLoading(true);
try {
if(body) {
body = JSON.stringify(body);
headers['Content-Type'] = 'application/json';
}
const response = await fetch(url, {method, body, headers});
const data = await response.json();
if(!response.ok) {
throw new Error(data.message || 'Something goes wrong')
}
setTimeout(() => {
setLoading(false);
}, 800);
return data
} catch (e) {
setLoading(false);
setError(e.message);
throw e;
}
}, []);
const clearError = useCallback(() => {setError(null)}, []);
return {loading, request, error, clearError}};
CreateUser.js
import React, {useCallback, useContext, useEffect, useState} from 'react';
import {useHttp} from "../hooks/http.hook";
import Button from "../components/Button/Button";
import {AuthContext} from "../context/auth.context";
import {Formik} from "formik";
export const CreateUser = () => {
const {token} = useContext(AuthContext);
const {loading, request} = useHttp();
const createUser = useCallback(async (body) => {
try {
const fetched = await request(`/api/user/create`, 'POST', body, {
Authorization: `Bearer ${token}`
});
} catch (e) {console.log(e)}
}, []);
const handleCreate = (values, {resetForm}) => {
console.log(values);
createUser(values);
// resetForm({});
};
return (
<div className="wrapper">
<div className="row">
<div className="column small-12 text-center color-white mb_45">
<div className="custom-headline text text-48 font-bold">
<h1>
Crate user
</h1>
</div>
</div>
</div>
<Formik
enableReinitialize
initialValues={{
name: '',
code: '',,
image: null
}}
onSubmit={handleCreate}
>
{({
values,
errors,
touched,
handleBlur,
handleChange,
handleSubmit,
isSubmitting,
setFieldValue,
resetForm
}) => (
<form onSubmit={handleSubmit} className="row align-center">
<div className="column small-12 large-7">
<div className="form-item flex-container align-middle mb_20">
<label className="text text-14 font-semibold font-uppercase text-right small-4">
Photos
</label>
<input id="image" type="file" name="image" className="file_input"
onChange={(event) => {
setFieldValue("image", event.currentTarget.files[0]);
}} />
</div>
</div>
<div className="column small-12 large-7">
<div className="form-item flex-container align-middle mb_20">
<label className="text text-14 font-semibold font-uppercase text-right small-4">
Name
</label>
<input
className="text text-17 "
type="text"
name="name"
onChange={handleChange}
onBlur={handleBlur}
value={values.name}
/>
</div>
</div>
<div className="column small-12 large-7">
<div className="form-item flex-container align-middle mb_20">
<label className="text text-14 font-semibold font-uppercase text-right small-4">
Code
</label>
<input
className="text text-17"
type="text"
name="code"
onChange={handleChange}
onBlur={handleBlur}
value={values.code}
/>
</div>
</div>
<div className="column small-12 mt_20">
<div className="btn_group flex-container flex-wrap align-middle align-center">
<Button className="btn-lg radius-8" theme="blue"
text={Submit} type="submit"
/>
</div>
</div>
</form>
)}
</Formik>
</div>
)
};
Ответ №1:
Оберните файл изображения с помощью FormData с помощью клавиши «изображение».
upload.single('image')
на переднем конце
const handleCreate = async (values) => {
try {
const body = new FormData();
body.append( "image", values.image);
...
} catch (err) {}
};
И убедитесь, что для вашего пути назначения используйте «dirname».
`${__dirname}/../client/public/uploads/`
Измените это в соответствии с вашим путем к каталогу
Ответ №2:
У меня есть 2-3 предложения, в вашем app.js файл
router.post('/create', upload.single('image'), auth, async (req, res) => {
console.log(req.file);
вы должны использовать промежуточное программное обеспечение auth перед использованием upload.single.
и вы должны отправить заголовки с запросом на публикацию с {content type: multipart/form-data}
Комментарии:
1. Я попробовал с данными формы, но все равно получаю ту же ошибку
Ответ №3:
ХОРОШО! Я не знаю, ПОЧЕМУ это было причиной , но я нашел решение — я использую axios
вместо fetch
, и, конечно FormData
, для загрузки изображений или файлов, и это работает!
Надеюсь, это может быть полезно для кого-то еще. Спасибо за все ответы.
const handleCreate = (values, {resetForm}) => {
const formData = new FormData();
formData.append('name', values.name);
formData.append('code', values.code);
formData.append('image', values.image);
axios.post('/api/user/create', formData)
.then(console.log)
catch(console.error);
resetForm({});
};