File Upload and download JavaScript - reactjs

EDIT
Hope someone can clarify this issue I am having:
I want to store files in the backend inBinary format, and after get them and convert them into the original state of the file, I believe there is something wrong with my code, I am using HTML upload
this is how I send the binary data to backend:
const handleSubmit = async file => {
let formData = new FormData()
formData.append('file', file)
try {
const res = await postInvoice(uuid, formData)
console.log(res)
setLoadingInvoice(false)
setState({
fileList: [],
attachments: [],
filesConverted: [],
})
}
}
**edit with the api call, only missing hte url part which is in a different file**
export const postInvoice = async (id, data) => {
return await request
.post(BRattachments().post(id))
.send(data)
.set(getHeaders())
}
this is how I try to download te bianrry data:
const handleDownload = async value => {
try {
const res = await getInvoice(value.id)//api call
const file = new Blob([response], {
type: res.type,
})
saveAs(file, value.id + '.' + res.extension)//using save as to save import saveAs from 'file-saver'
}
}
**edited,this is the API CALL,I have them groued in a different file**
export const getInvoice = async ref => {
return await request
.get(BRattachments().getAll(ref))
.send()
.set(getHeaders())
.responseType('blob')
}
the problem is that every time I try to download any saved file, it gives me a problem, with XLS I only see null in the file, with PNG it says file not PNG, etc.
Hope someone can clarify how can I download binary file and convert it in a downloadable file.
thanks

Related

Pass Files to Node Server

Im having this function in my react app, what is does is sending a file to the server and the server save it to the declared dir.
const handleUploadfile = (event) => {
event.preventDefault();
const data = new FormData();
data.append('file',file );
fetch("http://localhost:4000/upload", {
method: 'POST',
body: data
}).then((response) => console.log(response))
}
heres the code in the node server handling the request above
app.post("/upload", (req, res) => {
const newpath = "./files/";
const file = req.files.file;
const filename = file.name;
file.mv(`${newpath}${filename}`, (err) => {
if (err) {
res.status(500).send({ message: "File upload failed", code: 200 });
}
res.status(200).send({ message: "File Uploaded", code: 200 });
});
});
The upload is working well, but what i want to know is, am i able to include some data like a string directory for each uploaded file something like that.
BTW im using express-uploadfile middleware so i can access the files.
If you want to upload another filed in your request you simply need to call append on your FormData object.
const data = new FormData();
data.append('file',file );
data.append('string','some string');
Then read it on the server
const string = req.body.string;

Downloading Files from Firebase Storage

I have a function that when clicked downloads a file from firebase storage. I have implemented the function as in the firebase documentation v8. I have also applied the cors policy to allow downloads but the problem is that file is still not being downloaded and I am not getting an error.
Kindly help on this.
Below is my download function.
const handleDownloadFile = async file => {
try {
const url = file.downloadURL;
console.log(file);
const xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = event => {
const blob = xhr.response;
};
xhr.open('GET', url);
xhr.send();
} catch (error) {
console.log(error.message);
}
};
The function accepts the details of the file. Below is a sample object that
{
name: 'Ndov network LOGO - 400.png',
downloadURL: 'https://firebasestorage.googleapis.com/v0/b/tkiā€¦=media&token=2680cc-4043-4676-992a-7e64fe8342f2',
uuid: '1b1c0a4b-80a5-42d4-a698-719a26e3f281'
}
kindly help me understand why I am not getting any errors and the still download, not working.
Easiest way to download file on url that comes from API is to
window.open(url,'_blank');
or you can use some library for downloading files in blob format like
https://www.npmjs.com/package/file-saver
This will open a window and ask the user where they want to save the file locally.
import { getStorage, ref, uploadBytes, getBlob } from 'firebase/storage'
export const downloadFile = (refToFile) => {
const storage = getStorage()
const fileRef = ref(storage, refToFile)
const blob = await getBlob(fileRef)
const blobUrl = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = blobUrl
link.download = 'myfilename.pdf'
link.click()
}

Check setState has run before executing API call in React

I have a form with a text field and a form input which accepts multiple files. OnSubmit the files are sent to Firebase Storage, which sends back a URL for each file. These URLs are then stored in a 'photosURL' array in the form object, which is then posted to MongoDB.
The problem is, every time I post the form object data to Mongo, the photos array is empty, despite the console log showing it to be populated before I call the post-to-Mongo code. This leads me to think the post-to-Mongo code is using the form object value before it has been populated with the photo URLs.
The question is, how do I check that the photo array has been populated before I run the code to push the data to MongoDB? I'm already using a Promise.all to in theory wait for all the files to be sent and the URLs returned, but I can't work out why else the photoURLs array is empty every time data is sent to Mongo.
Here's the code:
const [form, setForm] = useState({
userId: '',
post: '',
createdAt: createdAt,
photoURLs: [],
})
const handleSubmit = (e) => {
e.preventDefault()
newPost ? postData(form) : ...
}
// SEND FILE TO FIREBASE AND GET BACK THE URL
async function handleUpload(file) {
const storageRef = useStorage.ref("PostImages");
const fileRef = storageRef.child(`${nanoid()}`);
return fileRef.put(file).then(() => {
return fileRef.getDownloadURL().then(function (url) {
photoArray.push(url);
setForm(prevState => ({ ...prevState, photos: photoArray }))
});
});
}
// POST FUNCTION
const postData = async (form) => {
setLoading(true)
let thisFileArray = fileInput.current.files;
const uploadTasks = [];
for (let i = 0; i < thisFileArray.length; i++) {
uploadTasks.push(handleUpload(thisFileArray[i]));
}
Promise.all(uploadTasks).then(() => {
axios.post('/api/posts', form)
.then(response => {
...
})
.catch(error => {
...
})
})
}
Can anyone see what's going wrong, please?
EDIT: This is a consolel log of the form object, called before the axios.post code (it's showing the photosURL as populated):
createdAt: 1630072305502
photos:
0: "https://firebasestorage.googleapis.com/..."
1: "https://firebasestorage.googleapis.com/..."
post: "sample text"
userId: "1iNGV..."
I think that you are running into a timing issue.
Don't forget that React state updates are asynchronous, as described here.
I suggest to pass your URLs directly instead of going through your component's state:
async function handleUpload(file) {
const storageRef = useStorage.ref("PostImages");
const fileRef = storageRef.child(`${nanoid()}`);
await fileRef.put(file);
const url = await fileRef.getDownloadURL();
return url; // Send back the download URL
}
const postData = async (form) => {
setLoading(true);
let thisFileArray = fileInput.current.files;
const uploadTasks = [];
for (let i = 0; i < thisFileArray.length; i++) {
uploadTasks.push(handleUpload(thisFileArray[i]));
}
const photos = await Promise.all(uploadTasks); // Get all URLs here
await axios.post('/api/posts', {...form, photos}); // Send URLs to your server
setLoading(false);
}
If I understood correct, You want to upload files first and when you get your urls array populated then only you want to call postData function?
If that's the case, then you can use useEffect to detect the urls change.
useEffect(() => {
// Here you call your postData function.
postData();
}, [form.photoURLs])
What this will do is, Whenever your form.photoURLs gets populated this useEffect will run and will make the request to the server with proper data.

How to add files using JSZip from remote URL in ReactJS?

I'm using the map function to loop files inside a folder of my zip files but the folder is always empty. The fetching of the main file is working though.
Here is my code:
getZip = async () => {
const zip = new JSZip();
const url = await svc.getMainFileURL(); // will return a downloadable url from s3
let response = await fetch(mcsURL); // will fetch the data from s3 blob
let data = await response.blob(); // and convert it into blob
zip.file(`MainFile.xlsx`, data); // add into the zip file
const otherFiles = zip.folder("files"); // create another folder inside the zip file
// Loop files from source data then insert it in the folder
const list = sourceData.map(async (item,index) => {
const fileUrl = await svc.getOtherFileUrl();
const response = await fetch(fileUrl);
const data = await response.blob();
console.log(data);
otherFiles.file(`${index}.xlsx`, data);
})
zip.generateAsync({type:"blob"})
.then(function(content) {
FileSaver.saveAs(content, `Zip File Name`);
});
}
The logging of data returns fine but when I download the zip file it has only the data of the main file with folder files. Here's a screenshot:
There may be a problem with me using async/await but I can't figure it out. Can anyone help me? Thanks in advance.
Nevermind, I already solved it using Promise.all(). The zip.generateAsync function returns the zip file even if the promises from the map function are not resolved. That's why the folder is always empty. Here is my altered code:
getZip = async () => {
const zip = new JSZip();
const url = await svc.getMainFileURL(); // will return a downloadable url from s3
let response = await fetch(mcsURL); // will fetch the data from s3 blob
let data = await response.blob(); // and convert it into blob
zip.file(`MainFile.xlsx`, data); // add into the zip file
const otherFiles = zip.folder("files"); // create another folder inside the zip file
// Loop files from source data then insert it in the folder
const list = sourceData.map(async (item,index) => {
const fileUrl = await svc.getOtherFileUrl();
const response = await fetch(fileUrl);
const data = await response.blob();
console.log(data);
otherFiles.file(`${index}.xlsx`, data);
return data;
})
// If all prmises are fullfilled it will call the zip
// function and download it using the FileSaver library
Promise.all(list).then(function() {
zip.generateAsync({type:"blob"})
.then(function(content) {
FileSaver.saveAs(content, `Zip File Name`);
});
});
}

fetching a plain/text with redux-api-middleware

Can I fetch a txt file with redux-api-middleware? If yes, how? According to the doc the body is set to undefined if not application/JSON compliant, but what I will get is a simple text/plain
Basically what I did was altering fetch action and doing my custom stuff with response data. Example:
fetch: async (...args) => {
const response = await fetch(...args);
if (response.ok && response.status === 200) {
const reader = response.body.getReader();
const type = response.headers.get('content-type');
const filename = filenameExtractor(response.headers.get('content-disposition'));
const { value } = await reader.read();
fileSaver({ value, filename, type });
}
return response;
},
This piece of code reads response contents and pushes them for download to the browser. filenameExtractor and fileSaver are custom functions. This way response is not altered.
Using such approach you can further modify response and create json yourself for data to be able to be saved in redux like:
const response = await fetch(...args);
return new Response(
JSON.stringify({ data: response.body })
);

Resources