React Laravel - File showing in state but not in controller - reactjs

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

Related

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

Reactjs updated prop is not shown

I tried to create a interactable map following this example here: https://docs.mapbox.com/mapbox-gl-js/example/cluster/
In my componentDidMount (where I create a mapboxgl) I implemented clickable markers, when clicked on the markers a popup appears which displays various informations.
After the click I want to call a second function (fetch) to get more data on that specific marker: this.props.getData(id);
I then want to display these data in the same popup as the other information.
My problem is that this.props.testdata is empty on the first click. If I double-click on the marker, the data appear. So my guess is that my component does not notice the change of the state/prop and therefore does not update?
How do I do that or what am I missing?
Map.js
this.map.on('click', 'unclustered-point', (e) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const id = e.features[0].properties.id;
const infos = e.features[0].properties.infos;
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
if (id == null) {
console.log("Missing id, cant get informations")
return;
}
this.props.getData(id);
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(
`
Id: ${id}
<br>
Infos: ${infos}
<br>
<br>
Testdata: ${this.props.testdata}
`
)
.addTo(this.map);
});
this.map.on('mouseenter', 'clusters', () => {
this.map.getCanvas().style.cursor = 'pointer';
});
this.map.on('mouseleave', 'clusters', () => {
this.map.getCanvas().style.cursor = '';
});
});
App.js (getData function):
getData = (id) => {
if (id== null) {
console.log("Missing id")
return;
}
const {mapCenter, startDate, endDate} = this.state;
const neo4j = require('neo4j-driver')
const driver = neo4j.driver('bolt://xxx', neo4j.auth.basic("xx", "xx-xx"))
const session = driver.session()
session
.run('Here goes a neo4j cypher statment',{id: id})
.then((results)=> {
const data= [];
results.records.forEach((record) => data.push([record.get("r"), record.get("n"), record.get("b")]))
this.setState({
data
});
session.close()
driver.close()
}).catch(e => {
console.log(e)
session.close();
});
};
I am not familiar with neo4j, but it is apparent that getData(id) fetches data from a server. This is going to be an asynchronous operation, so you should add a state property to maybe show a spinner while data is being fetched?
Regarding testdata not being available, I do not see the code where it is being set.
Maybe your setState code should be:
this.setState({
testdata: data
});
//If your data prop is testdata.
As per the current setState, data property of your component state would be set with server response.
Updates:
Temporary fix for async server call:
You can change following methods and try if it fixes your issue:
this.map.on('click', 'unclustered-point', async (e) => {
// ...previous code
await this.props.getData(id);
// This forces the following code to execute synchronously. Basically it should wait for your API call to be complete
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(
`
Id: ${id}
<br>
Infos: ${infos}
<br>
<br>
Testdata: ${this.props.testdata}
`
)
.addTo(this.map);
});
this.map.on('mouseenter', 'clusters', () => {
this.map.getCanvas().style.cursor = 'pointer';
});
this.map.on('mouseleave', 'clusters', () => {
this.map.getCanvas().style.cursor = '';
});
});
getData = (id) => {
//... previous code
// we return a promise to use await in the onClick handler
return session
.run('Here goes a neo4j cypher statment',{id: id})
.then((results)=> {
const data= [];
results.records.forEach((record) => data.push([record.get("r"), record.get("n"), record.get("b")]))
this.setState({
data
});
session.close()
driver.close()
}).catch(e => {
console.log(e)
session.close();
});
}
If you are still facing an issue, please create a sample app and share.
I have not yet managed to fix the original problem.
However, I have found another solution:
In my Map.js I'm calling the this.props.testdata in th UI like this:
<div className="sidebar">
info: {JSON.stringify(this.props.testdata)}
</div>

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.

MERN+ Cloudinary: Unsupported source URL

I'm trying to upload file to cloudinary. Here is part of my react component
...
addItem() {
...
let file = this.fileInput.value;
keywords !== "" && this.props.onAddItem(keywords, place, image);
...
}
render() {
return (
....
<Input
type="file"
innerRef={(input) => {this.fileInput = input}}
name="image"
id="image"
placeholder=""/>
)
}
Here is action file:
export function onAddItem(keywords, place, file, id, isChangebale = false) {
return (dispatch) => {
axios.all([
axios.post('https://api.cloudinary.com/v1_1/myservername/image/upload',
{upload_preset: "mypresetname", file: file}),
axios.post('http://localhost:3001/api/items/', { keywords, place, id, isChangebale })
])
.then(axios.spread((cloudinaryRes, localRes) => {
console.log(cloudinaryRes, localRes);
}))
I receive error xhr.js:178 POST https://api.cloudinary.com/v1_1/testovich/image/upload 400 (Bad Request) and in response headers "X-Cld-Error: Unsupported source URL: C:\fakepath\2017-12-07_19-06-445.png"
When I test using postman I have correct response.
So it looks like I do something wrong when pass file from rect component to action file. How to pass correct path/file to cloudinary?
There were two mistakes:
1. in react component there should be
let file = this.fileInput.files[0];//I upload only one file
instead of
let file = this.fileInput.value;
in action file
export function onAddItem(keywords, place, image, id, isChangebale = false) {
const formData = new FormData();
formData.append("file", image);
formData.append("upload_preset", "mypresetname");
return (dispatch) => {
axios.all([
// AJAX upload request using Axios )
axios.post('https://api.cloudinary.com/v1_1/myservername/image/upload',
formData,
instead of:
export function onAddItem(keywords, place, file, id, isChangebale = false) {
return (dispatch) => {
axios.all([
axios.post('https://api.cloudinary.com/v1_1/myservername/image/upload',
{upload_preset: "mypresetname", file: file}),
Convert the image to a base64 like const base64Img = data:image/jpg;base64,${file.data};
The file.data represents the data property from response from image picker.
Then I passed the base64Img to data like
return RNFetchBlob.fetch('POST', apiUrl, headerProps, [ { name: 'file', fileName: file.fileName, type: file.type, data: base64Img } ]);
Hope it helps.

Multer upload showing file as undefined in express 4

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"

Resources