I am using two function two handle multiple files upload in ReactJS.
const doSomethingAsync = (file, formD)=>{
return new Promise((resolve) =>{
const type = file.type.split("/")[0];
const filename = file.name;
// file reader to preview file
let reader = new FileReader();
reader.onload = () => {
formD.append("fileList", file, filename);
setSelectedFileForUpload((prevState) => [
...prevState,
{
file: reader.result,
type: type,
name: filename,
},
]);
};
reader.readAsDataURL(file);
});
}
const handleFileUpload = (e) => {
const files = e.target.files;
if (!files) return false;
let formD = new FormData();
console.log("START");
for (const file of files) {
console.log("MID");
doSomethingAsync(file,formD);
}
console.log("END");
for (const file of formD) {
console.log(file); // not getting file. Don't know why.
}
};
If I use want to upload two files (or one file). Then
OUTPUT (if we upload two files) :
START
MID
MID
END
As you can see I am looping through formData by using :
for (const file of formD) {
console.log(file); // not getting file. Don't know why.
}
But it is NOT executing (may be because of length is 0).
So I want to get formData after for of loop completed.
Related
I'm taking input as a PDF file and using javascript to add custom metadata, but I'm not getting a satisfactory result.
Below is a sample method code that I used to add custom metadata that is first converted to blob type and then added, but when we convert its blob data to base64 and download the file and check the properties, we cannot find it.
const blobToBase64 = (blob: any) =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
});
const updatePDFMetaData = (file: any, metadata: any) => {
let convertBlobToBase64: any;
const selectedFile = file;
const reader = new FileReader();
reader.readAsArrayBuffer(selectedFile);
reader.onload = async (event:any) => {
const fileBuffer: any = event?.target?.result;
const blob: any = new Blob([fileBuffer], { type: selectedFile.type });
Object.keys(metadata).forEach((key: any) => {
blob[key] = metadata[key];
});
convertBlobToBase64 = await blobToBase64(blob);
console.log("convertBlobToBase64", convertBlobToBase64);
};
};
I use lodash clonedeep for uploading files.
I wrote a function that forbids uploading identical files. But if I delete some file after uploading, it still stays in state and I can't upload file with the same name.
What can I do to get the file removed from the state too?
const [files, setFiles] = useState([]);
//state to store uploaded file's name
const [fileNames, setFileNames] = useState([]);
const onSelectFile = (e) => {
try {
let fileArr = cloneDeep(files);
let promises = [];
for (let file of e.target.files) {
promises.push(
new Promise((resolve, reject) => {
const fileName = file.name
//if the file has not been already uploaded
if (!fileNames.includes(fileName)) {
//add the current fileName in state
setFileNames([fileName, ...fileNames]);
const type = file.type;
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (evt) {
const fileData = evt.target.result;
fileArr.push({
name: fileName,
type: type,
data: fileData,
comment: "",
id: `${new Date().getTime()}_${fileName}`,
canDelete: true
});
if (typeof props.onFileSelected == "function")
props.onFileSelected(fileArr);
resolve(true);
}
reader.onerror = function (evt) {
console.log("error reading file");
reject(false);
}
} else {
alert("File has already been uploaded");
reject(false);
}
})
);
}
Promise.all(promises).then(r => {
setFiles(fileArr);
})
}
catch(e) {
console.log(e);
}
}
I don’t know what to do, it took me 40 hours to think, but I still didn’t understand anything.
From what I can understand from your question, you aren't saving the images on disk. You also haven't included the logic you are using for deleting files. Either way, the implementation is similar.
So when a user deletes a file, assuming they are deleting by filename, we use the filter() method to only keep those that aren't the file we want to delete.
const fileNameToRemove = 'example.txt';
setFiles(files.filter(file=> file.name !== fileNameToRemove));
setFileNames(fileNames.filter(name => name !== fileNameToRemove));
So you will want to do something like this, I haven't used clonedeep like you're in this example, but it's a quick add. I have also moved the read file section into its own function, and am I returning a promise, so I can use async/await within the core upload function.
const [files, setFiles] = useState([]);
const [fileNames, setFileNames] = useState([]);
const readFileAsync = async (file) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.readAsArrayBuffer(file);
fileReader.onload = () => {
resolve(fileReader.result);
}
fileReader.onerror = () => {
reject(null);
}
})
}
const onSelectFile = async (e) => {
const uploadingFiles = e.target.files;
for (const file of uploadingFiles) {
const fileName = file.name;
if (fileNames.includes(fileName)) {
console.error("File duplicate");
continue;
}
try {
const fileContentsBuffer = await readFileAsync(file);
setFiles([...files, {
name: fileName,
type: file.type,
data: fileContentsBuffer,
comment: "",
id: `${new Date().getTime()}_${fileName}`,
canDelete: true
}])
setFileNames([...fileNames, fileName]);
} catch (e) {
console.error("Error reading file");
continue;
}
}
}
const deleteFile = (fileName) => {
const fileDataToDelete = files.find((file) => {
return file.name === fileName;
})
if (!fileDataToDelete.canDelete) {
console.error('Can\'t delete file!');
return;
}
setFileNames(fileNames.filter(name => name != fileName));
setFiles(files.filter(file => file.name != fileName && file.canDelete));
}
I want to read the contents of the file directly by using the file path. I can do this by having the file selected. But I don't know how to do it using the direct file path. I could not find any examples or sources for this. Below is how I read the file by selecting it from the input.
import * as XLSX from 'xlsx';
var items = [];
readExcel = (file) => {
const promise = new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.readAsArrayBuffer(file);
fileReader.onload = (e) => {
const bufferArray = e.target.result;
const wb = XLSX.read(bufferArray, { type: "buffer" });
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
const data = XLSX.utils.sheet_to_json(ws);
resolve(data);
};
fileReader.onerror = (error) => {
reject(error);
};
});
promise.then((d) => {
this.items = d;
console.log(this.items)
// fill dictionary
this.dictionary = Object.assign({}, ...this.items.map((x) => ({ [x.PartNumber]: x.Cost })));
console.log(this.dictionary)
});
};
<input
type="file"
onChange={(e) => {
const file = e.target.files[0];
this.readExcel(file);
}}
/>
I beleive it should work:
const req = new XMLHttpRequest();
req.responseType = "arraybuffer";
req.open("GET", "https://.../MyExcelFile.xlsx", true);
req.onload = () => {
const bufferArray = req.response;
const wb = XLSX.read(bufferArray, { type: "buffer" });
...
I couldn't find a direct read operation. I converted the excel file to json format and got my job done.
I was trying to create an API endpoint for rotating images uploaded from client side. I'm sending images as base64 type, converted from blob (from simple <input tag), as follows:
const addImageBase64 = async (fileData) => {
const file = fileData;
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event) => {
resolve(event.target.result);
};
reader.onerror = (err) => {
reject(err);
};
reader.readAsDataURL(file);
});
};
Then, on the server side, that's how the endpoint looks like:
app.post("/api/rotate-image", async (req, res) => {
try {
let buffer = Buffer.from(req.body.imageData, "base64"); //not working
let array = new Uint8Array(buffer); //not working
const image = await sharp(buffer)
.rotate(180)
.png({ quality: 100 })
.toBuffer();
console.log("success");
res.status(200).send({
success: true,
result: image,
});
} catch (e) {
console.warn(e);
}
});
And here, every my attempt is ending up with '[Error: Input buffer contains unsupported image format]' - either for Buffer or Uint8Array. Can anyone help me with this issue? What is the right input type for Sharp that acctually works?
Edit:
Error with logged buffer obj:
I'm trying to upload an image using Laravel as a backend and Vue.js2 as a frontend.
Here's my code
addUser() {
let formData = new FormData();
formData.append('fullname', this.newUser.fullname);
formData.append('email', this.newUser.email);
formData.append('phone', this.newUser.phone);
formData.append('photo', this.newUser.photo);
formData.append('roles', this.newUser.roles);
formData.append('permissions', this.newUser.permissions);
axios.post('users', formData)
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
});
},
onFileChange(e) {
let files = e.target.files || e.dataTransfer.files;
if (!files.length)
return;
this.createImage(files[0]);
},
createImage(file) {
let reader = new FileReader();
let vm = this;
reader.onload = (e) => {
vm.newUser.photo = e.target.result;
};
reader.readAsDataURL(file);
},
And Laravel code on a backend:
if($request->hasFile('photo')) {
return response()->json([
'success' => true,
'message' => 'Файл есть.',
'data' => $request->all()
]);
}
return response()->json([
'success' => true
]);
Finally, the html code:
<input type="file" class="filestyle" data-text="Загрузите фото" #change="onFileChange">
Unfortunately, it doesn't seem to work. File has not been found. What's the workaround?
I doubt that the image gets attached to the request sent because the line formData.append('photo', this.newUser.photo); only appends the file's temporary url to the formData due to the fact that the property photo of the newUser object was set to the temporary url of the file inside your FileReader onload method: vm.newUser.photo = e.target.result;.
You should attach the file to the formData and not the temporary url
To do that, you might want to change your createImage(file) function to:
createImage(file) {
let reader = new FileReader();
this.newUser.photo = file;
let vm = this;
reader.onload = (e) => {
vm.newUser.photo_preview = e.target.result;
};
reader.readAsDataURL(file);
},
and you can use this.newUser.photo_preview for showing the photo preview wherever you like in your view.
Hope that helps :)
Solved
I simply deleted createImage(file) method and changed onFileChange(e) method like this:
onFileChange(e) {
let files = e.target.files || e.dataTransfer.files;
if (!files.length)
return;
this.newUser.photo = files[0];
}
And everything worked perfecly. Thanks for everyone for spending time and help...)