Multer upload showing file as undefined in express 4 - reactjs

I'm using React/redux to post some form values to an express server.
I'm also using Multer for file upload. Everything is working except the Multer file upload.
I'm posting my setup below in hopes someone else has had this issue. I have seen lots of posts with similar issues but nothing that seems to fix the solution for me so not trying to duplicate the issue just trying to solve the issue.
JSX form
<form onSubmit={this.save} ref='form' role="save" encType="multipart/form-data">
<div className="laptop">
<input type='file' alt="project-image" name="photo" />
</div>
<div className="edit-title edit-field-wrapper">
<input type="text" className="form-control" name="title" />
</div>
<div className="edit-description edit-field-wrapper">
<textarea type="text" className="form-control" name="description" />
</div>
</form>
// Save helper function to gather form input values to be posted back to express
save( event ){
event.preventDefault();
let elements = [... findDOMNode(this.refs.form).elements];
let formData = {}, el, val;
Object.keys(elements).map((key) => {
el = elements[key];
if(el.nodeName && el.nodeName!=='BUTTON'){
val = (el.name == 'skills') ? el.value.split(',') : el.value;
if(val.includes("fakepath")) val = val.replace("C:\\fakepath\\", "");
formData[el.name] = val;
}
});
console.log("formData = ", formData);
this.props.update( formData );
}
My action creator
export function editProject( project ){
const headerConfig = {headers: { authorization: getLocalToken()}};
return ( dispatch ) => {
const dbPost = axios.post(`${config.env.ROOT_URL}/api/file`, project,
headerConfig);
return dbPost.then(result => {
dispatch({
type: type.EDIT_PROJECT,
payload: result.data
});
}).catch( error => {
return error.response.data;
});
}
}
Route file in express
const upload = multer({ dest: './public'});
app.post('/api/file', upload.single('photo'), (req, res) => {
console.log("BODY = ", req.body); //form fields
console.log("FILE = ", req.file); //form files
});
I'm using app.use(bodyParser.json()); so I can get my header authorization with a jwt token in it to show in req.body for other routes.
Body comes back with all my posted formData but req.file comes back undefined every-time. This is driving me crazy for the past 10 hours. I've tried removing boadParser but still nothing. I have read body-parsey doesn't support multipart forms. but I thought Multer doesn't need body parser so why is this still coming back with file undefined.
I'm using the following
"express": "^4.14.1"
"body-parser": "^1.17.1"
"multer": "^1.3.0"

Related

MongoDB + React - Obtain a document from db based on a query I am passing

I am trying to access a document from my MongoDB Atlas db that contains a specific key, based on a query I am passing in the fetch. I've followed the guides on the backend setup from MongoDB, it's all working, I'm connected to it, and now here's what I'm trying to do:
Documents look like this:
{
invitationCode: string;
name: string;
numberOfPeople: number;
specialMentions: string;
confirmed: boolean;
}
In the frontend, there's only one input at first, where the user should be entering his invitation Code. Once he clicks on the button, a request should be made to the BE, sending the value he entered. The BE should look through the documents and find the document that contains the invitationCode mathing with the input (The invitation codes are all unique). After the BE identified the document, it should be sent back to the frontend, so I can juggle with it here (display the name of the person, show the other 3 fields, etc.)
Here's what I have so far:
in my record.js file (backend):
const express = require("express");
const recordRoutes = express.Router();
const dbo = require("../db/conn");
const ObjectId = require("mongodb").ObjectId;
recordRoutes.route('/record/invitations').post(function (req, res) {
let db_connect = dbo.getDb();
let myquery = req.body.invitationNumber;
console.log('MYQUERY:', myquery);
db_connect
.collection('records')
.findOne({zzceva: myquery}, function (err, result) {
if (err) throw err;
console.log('RESULT FROM BE', result);
res.send(result);
})
console.log('QUERY:', myquery);
})
and in the frontend I have this logic:
const onSubmit = useCallback(async (e) => {
e.preventDefault();
if (personEnteredCode) {
const newPerson = { ...form };
await fetch("http://localhost:5000/record/add", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(newPerson),
})
.catch(error => {
window.alert(error);
return;
});
setForm({ invitationNumber: "", numberOfPeople: "", specialMentions: "" });
navigate("/");
console.log('newPerson:', newPerson);
} else {
// THIS IS WHAT DOES NOT WORK >>>>>>>>>>>>>>>>>>>>>>>>>>>
// I AM TRYING TO SEND THE invCode back to the BE
const invCode = form.invitationNumber;
await fetch("http://localhost:5000/record/invitations", {
method: 'POST',
body: JSON.stringify(invCode),
})
.then((response) => {
console.log('THE RESPONSE IS:', response);
setCurrentPerson(response);
})
.catch(error => {
window.alert(error);
return;
})
.finally(() => setPersonEnteredCode(true))
}
// When a post request is sent to the create url, we'll add a new record to the database.
}, [form, navigate, personEnteredCode])
return (
<div className="confirm-form">
<form onSubmit={onSubmit}>
<div className="form-group">
<label htmlFor="invitationCode">Cod invitație:</label>
<input
type="text"
className="form-control"
id="invitationCode"
value={form.invitationNumber}
onChange={(e) => updateForm({ invitationNumber: e.target.value })}
/>
</div>
{!personEnteredCode && <input type={'submit'} value={'OK'}/>}
{personEnteredCode && <div className="form-group">
<label htmlFor="numberOfPeople">Număr persoane:</label>
<input
type="number"
className="form-control"
id="numberOfPeople"
value={form.numberOfPeople}
onChange={(e) => updateForm({ numberOfPeople: e.target.value })}
/>
</div>}
{personEnteredCode && <div className="form-group">
<div className="form-check form-check-inline">
<label htmlFor="specialMentions">Mențiuni speciale:</label>
<input
type="text"
className="form-control"
id="specialMentions"
value={form.specialMentions}
onChange={(e) => updateForm({ specialMentions: e.target.value })}
/>
</div>
</div>}
{personEnteredCode &&<div className="form-group">
<input
type="submit"
value="Confirmă"
className="btn btn-primary"
/>
</div>}
</form>
</div>
);
}
After many different tries, now the response I'm getting is 200 (not 404 not found or 500 like the first tries), but on the response object, I don't see the information I need, instead this is how a console.log looks like:
HUGE thanks in advance for any kind of guidance or help you could provide. I'm trying to understand what I'm doing wrong.
The issue is that you're logging the fetch response and not the data in the response body (so congrats! you're getting a response!).
The fetch response has a couple of different methods that you can use to read the data in the body. Depending on the type of data your API is returning, you'll use the appropriate method (.json, .text, .blob, etc.). These methods return a promise meaning they are asynchronous. Here's how you might modify your code:
fetch("http://localhost:5000/record/invitations", {
method: 'POST',
body: JSON.stringify(invCode)
})
.then((response) => {
return response.json()
})
.then((data) => {
//now you've got the data to put in state
setCurrentPerson(response);
})
.catch(error => {
window.alert(error);
return;
})
}
I can see that in your Express route, you're using res.send(result). You'll probably want to change that to: res.json(result). Both behave the same if you pass an object or array, but res.json() will explicitly convert your results to JSON.
Also, you didn't ask about it, but generally, you wouldn't use POST for this route. In REST, this would be a GET route and you'd generally pass the data as a param or querystring to your API.

PUT Request - Uploading a file - React / Express / Multer - req.file = undefined

I'm developping a social network for a school project, and I want to allow the user to update his/her information, specifically the profile Photo and the cover Photo.
I use multer for storing images.
When I try to upload an image using a POST request, it works perfectly fine but on a PUT request it says req.file /req.files is always undefined.
// FORM (IMPORTANT PART)
<form
className="update__form"
onSubmit={handleSubmit}
encType="multipart/form-data"
id="form"
>
{/* GESTION PHOTO DE COUVERTURE */}
<div className="update__form-cover">
<input
type="file"
name="coverPhotoUrl"
className="update__form-cover-input"
id="cover"
accept="image/*"
onChange={handleCover}
/>
<div className="update__form-cover-button">
Modifier la photo de couverture
</div>
</div>
<div
className={
loadCover === true
? 'update__form-cover-img'
: 'update__form-cover-img--close'
}
>
<img id="coverImg" alt="ok" />
</div>
{/* GESTION PHOTO DE PROFIL */}
<div className="update__form-profile">
<input
type="file"
name="profilePhotoUrl"
className="update__form-profile-input"
id="profile"
accept="image/*"
onChange={handleProfile}
/>
<div className="update__form-profile-button">
Modifier la photo de profil
</div>
</div>
<div
// MY DIFFERENTS FUNCTIONS
// TO DISPLAY AND STORE THE NEW COVER (USESTATE)
const handleCover = () => {
const coverChange = document.getElementById('cover').files
if (coverChange.length > 0) {
const fileReader = new FileReader()
fileReader.onload = function (event) {
document
.getElementById('coverImg')
.setAttribute('src', event.target.result)
setLoadCover(true)
setData({
...data,
coverPhotoUrl: coverChange[0],
})
}
fileReader.readAsDataURL(coverChange[0])
}
}
// DISPLAY AND STORE THE NEW PROFILE PHOTO (USESTATE)
const handleProfile = () => {
const profileChange = document.getElementById('profile').files
setData({
...data,
profilePhotoUrl: profileChange[0].name,
})
if (profileChange.length > 0) {
const test = new FileReader()
test.onload = function (event) {
document
.getElementById('profileImg')
.setAttribute('src', event.target.result)
setLoadProfile(true)
}
test.readAsDataURL(profileChange[0])
}
}
// FUNCTION CALLED WHEN FORM IS SUBMITTED
const handleSubmit = (event) => {
event.preventDefault()
try {
updateUser(data)
} catch (err) {
console.log(err)
}
}
// FUNCTION TO FETCH PUT
const updateUser = (data) => {
console.log(data)
const userId = localStorage.getItem('userId')
fetch('http://localhost:8000/api/user/' + userId, {
method: 'PUT',
headers: {
'Content-Type': 'form-data',
},
body: JSON.stringify(data),
})
}
export default updateUser
// BACK CONFIG
const multer = require('multer');
const MIME_TYPES = {
'image/jpg': 'jpg',
'image/jpeg': 'jpeg',
'image/png': 'png',
'image/svg': 'svg',
}
const storage = multer.diskStorage({
destination: (req, file, callback) => {
callback(null, '../images')
},
filename: (req, file, callback) => {
const name = file.originalname.split(' ').join('_');
const extension = MIME_TYPES[file.mimetype];
callback(null, name + Date.now() + '.' + extension);
}
});
const upload = multer({ storage: storage });
router.put('/:id', upload.array(), userCtrl.updateUser);
// CONTROLLER (not very important HERE BUT RETURN REQ.FILE UNDEFINED)
exports.updateUser = ((req, res, next) => {
console.log(req.file)
console.log(req.files)
const userInfos = req.file ? {
...JSON.parse(req.body.data),
coverPhotoUrl: `${req.protocol}://${req.get('host')}/images/${req.file.filename}`
} : {
...req.body
};
delete userInfos._userId;
User.findOne({
_id: req.params.id
})
.then((user)=> {
User.updateOne({
_id: req.params.id
},
{
...userInfos,
_id: req.params.id
})
.then(()=> res.status(200).json({ message : 'infos mises à jour ! '}))
.catch((error)=> res.status((401)).json({ error }));
})
.catch((error)=> res.status(400).json({ error }));
});
If someone come on this, i've finally found the answer :
first : use the formData constrcutor form-data-infos mdn, to put your datas in (formdata.append())
second : on the function who will fetch (front to back) : just remove the 'content-type', the browser will automatically set this,
it should run normally then
I also want to apologize for this 'useless' post because all the answers were on this forum (and on the web), my eyes were just closed

Uploading images from react with laravel api

I'm having trouble uploading files from a react input using a laravel API.
I'm working with react-hook-form.
My form and onSave are as follows
const onSave = data => {
// data.picture = imgs; here I tried changing the picture to event.target.files from the file input, didn't work either.
axios.defaults.headers.common["Authorization"] = "Bearer " + token;
axios
.post(`/api/products/store`, data, {})
.then(res => {
console.log(res);
})
.catch(err => console.log(err));
};
return (
<form onSubmit={handleSubmit(onSave)} encType="multipart/form-data">
<input
type="file"
name="picture[]"
label="Product Picture"
onChange={handlePicInput}
className={classes.inputFile}
multiple
/>
//other inputs
</form>
);
my post request leads to this controller method
public function store(Request $request)
{
$imageNames = '';
$pictures = (object) $request->file('picture');
//$pictures = $request->allFiles();
//$pictures = (object) $request->file('picture[]');
//$pictures = (object) $request->files;
foreach ($pictures as $key => $picture) {
/*WHEN I'M USING POSTMAN OR INSOMNIA,
this foreach loop is accessed but
the react form just skips the foreach completely */
$imageNames = $imageNames . $picture->store('product_pictures', 'public') . ',';
}
$product = Product::create([
'name' => $request->name,
'prices_amountmax' => $request->prices_amountmax,
'prices_amountmin' => $request->prices_amountmax,
'brand' => $request->brand,
'manufacturer' => $request->manufacturer,
'weight' => $request->weight,
'category_id' => $request->category_id,
'stock' => $request->stock,
'imageurls' => $imageNames
]);
$product->save();
}
To sum up, I tested uploading images with postman, it works just fine, so the problem must be in the react form?
Thank you for any kind of help
To upload images using js you can use FormData. I can't see your handlePicInput method to understand how input change is handled, but may be this snippet can help you to understand what to do further.
function handlePicInput(event){
let images = event.target.files
let fd = new FormData()
fd.append("images", images);
}
Then you can append to fd your other values and send via axios
axios.post(`/api/products/store`, fd)
Again, where to place the code and how to handle other inputs you have to manage by yourself, or provide more data
Try sending it as formData, with multiple files:
const onSave = data => {
const formData = new FormData();
for (let i in data) {
if(i === 'picture[]'){
for(let file of data[i]){
formData.append('picture',file);
}
}else{
formData.append(i, data[i])
}
}
// data.picture = imgs; here I tried changing the picture to event.target.files from the file input, didn't work either.
axios.defaults.headers.common["Authorization"] = "Bearer " + token;
axios
.post(`/api/products/store`, formData, {})
.then(res => {
console.log(res);
})
.catch(err => console.log(err));
};
I tested it with my Node/Express backend and it seems to work. "picture" will be an array of files. If your php backend doesn't recognize this correctly, try changing the formData.append('picture',file) to formData.append('picture[]',file), but then you'll also need to change the name in your php.

Chunked uploads with react-dropzone

I would like to upload chunked files using React-Dropzone. I have the following code:
OnDrop:
const dropTest = async (file, rejectedFiles) => {
var formData = new FormData();
formData.append('file', file);
try {
const response = await fetch(appContext.api_url + 'ApiUser/fileUpload', {
method: 'POST',
body: formData
})
const result = await response.json();
} catch (error) {
console.error('Error:', error);
}
}
Render:
<Dropzone onDrop={dropTest} chunking={true}>
{({getRootProps, getInputProps}) => (
<section>
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
</section>
)}
</Dropzone>
Dropzone library has an api, name of {chunking: true}, but It's not working on react-dropzone, how can I archieve this?
If it is not possible with React-Dropzone, I can get suggestions about another chunking upload solutions for react.
As far as I know, react-dropzone does not support chunking.
Those features are available in the fineuploader librairie (it is supported in REACT here is the link).
It gives you the ability to build DropZone, ProgressBar, Chunking, Retry and much more. It is also well documented.

React Laravel - File showing in state but not in controller

My project has React as frontend and Laravel as backend.
I am trying to upload a file and the details of the file appear in state while uploading but not in the controller and hence not able to upload it in folder.
Component has
<div className="form-group col-sm-4">
<div className="imgPreview">{$imagePreview}</div>
<label>Profile Pic</label>
<input className="form-control" type="file" name="profile_pic" onChange={(e)=>this._handleImageChange(e)}/>
</div>
further codes are:
_handleImageChange(e) {
e.preventDefault();
let reader = new FileReader();
let file = e.target.files[0];
reader.onloadend = () => {
this.setState({
fileselected: file,
profile_pic: file.name,
imagePreviewUrl: reader.result
});
}
reader.readAsDataURL(file)
}
Data gets uploaded though axios
submitHandler = e =>{
e.preventDefault()
this.props.drprofilecreate(this.state)
}
export const drprofilecreate = (data) => dispatch =>{
console.log("Coming form profile create action ")
console.log(data)
return axios.post('/api/v1/drProfileCreate', data)
.then( res =>res.data )
.then(drprofile =>
dispatch({
type: DRPROFILECREATE,
payload: drprofile,
}),
)
}
When I view the data being uploaded, it shows the file with its details like name, size etc. But the same does not come in the controller. It shows a blank array.
public function drProfileCreate(Request $request){
$data = $request->all();
$response = [
'success' =>true,
'datax' =>'Dr Profile uploaded in Contorller. Check',
'data' => $data
];
return response()->json($response, 201);
}
Hence Iam not able to upload the image. Help please. Appreciated

Resources