I'm trying to download files from an S3 bucket into .csv format directly from the client side using React. I've been able to upload files no problem, and even list all files in the bucket but am having trouble using getObject to actually download a file. The files I want are held within a templates folder, so when one is selected from dropdown, the Key in my params is changed and will grab that particular file.
I have tried using Blob to take the data.Body that is received from calling getObject on the specific bucket and file itself, but am having issues where some of the files don't download or it works on Mac OS but not on Windows, or doesn't work on Firefox but works in Chrome or vice versa.
I'm wondering if there is an easy way to do this using solely React? Taking the data received from the file and converting it to .csv?
This is my Downloads.js file, using React Bootstrap as a dropdown menu to be able to select a particular file name:
import React, { useState } from 'react';
import { ListGroup, Dropdown } from 'react-bootstrap';
import AWS from 'aws-sdk';
const InputDownload = () => {
const [template, setTemplate] = useState('Choose Template');
AWS.config.update({
accessKeyId: process.env.REACT_APP_ACCESS_ID,
secretAccessKey: process.env.REACT_APP_ACCESS_KEY,
});
const handleDownload = () => {
const s3 = new AWS.S3();
const params = {
Bucket: process.env.REACT_APP_INTERNAL_BUCKET_NAME,
Key: `templates/${template}`,
};
s3.getObject(params, (err, data) => {
if (err) {
console.log(err, err.stack);
} else {
console.log(data.Body.toString();
}
});
}
return (
<>
<form className='bg-white my-4'>
<Dropdown>
<Dropdown.Toggle variant='secondary' id='dropdown-basic'>
{template}
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item onSelect={() => setTemplate('T1')}>
T1</Dropdown.Item>
<Dropdown.Item onSelect={() => setTemplate('IV1')}>
IV1
</Dropdown.Item>
<Dropdown.Item onSelect={() => setTemplate('IV2')}>
IV2
</Dropdown.Item>
<Dropdown.Item onSelect={() => setTemplate('DV1')}>
DV1
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<input
type='submit'
value='Download'
className='btn btn-primary btn-block mt-3'
onClick={handleDownload}
/>
</form>
</>
);
};
export default InputDownload;
Any help would be greatly appreciated!
I'm trying to download files from an S3 bucket into .csv format directly from the client side using React. I've been able to upload files no problem, and even list all files in the bucket but am having trouble using getObject to actually download a file. The files I want are held within a templates folder, so when one is selected from dropdown, the Key in my params is changed and will grab that particular file.
I have tried using Blob to take the data.Body that is received from calling getObject on the specific bucket and file itself, but am having issues where some of the files don't download or it works on Mac OS but not on Windows, or doesn't work on Firefox but works in Chrome or vice versa.
I'm wondering if there is an easy way to do this using solely React? Taking the data received from the file and converting it to .csv?
This is my Downloads.js file, using React Bootstrap as a dropdown menu to be able to select a particular file name:
import React, { useState } from 'react';
import { ListGroup, Dropdown } from 'react-bootstrap';
import AWS from 'aws-sdk';
const InputDownload = () => {
const [template, setTemplate] = useState('Choose Template');
AWS.config.update({
accessKeyId: process.env.REACT_APP_ACCESS_ID,
secretAccessKey: process.env.REACT_APP_ACCESS_KEY,
});
const handleDownload = () => {
const s3 = new AWS.S3();
const params = {
Bucket: process.env.REACT_APP_INTERNAL_BUCKET_NAME,
Key: `templates/${template}`,
};
s3.getObject(params, (err, data) => {
if (err) {
console.log(err, err.stack);
} else {
console.log(data.Body.toString();
}
});
}
return (
<>
<form className='bg-white my-4'>
<Dropdown>
<Dropdown.Toggle variant='secondary' id='dropdown-basic'>
{template}
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item onSelect={() => setTemplate('T1')}>
T1</Dropdown.Item>
<Dropdown.Item onSelect={() => setTemplate('IV1')}>
IV1
</Dropdown.Item>
<Dropdown.Item onSelect={() => setTemplate('IV2')}>
IV2
</Dropdown.Item>
<Dropdown.Item onSelect={() => setTemplate('DV1')}>
DV1
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<input
type='submit'
value='Download'
className='btn btn-primary btn-block mt-3'
onClick={handleDownload}
/>
</form>
</>
);
};
export default InputDownload;
Any help would be greatly appreciated!
Share Improve this question asked Mar 10, 2021 at 14:08 AsherAsher 3173 gold badges9 silver badges23 bronze badges 2- What is your goal here? Is it to read the contents of the file so your React app can use that content in some way, or is it to actually download the file to the user's puter? Is there a server-side ponent to this app that can make AWS API calls on behalf of the client? – jarmod Commented Mar 10, 2021 at 14:31
- Thanks for your reply. The goal is for the user on the client side to be able to download the file in csv format. I actually figured out how to do this in the end using Blob but was having issues due to certain features being deprecated. I will answer this myself now, many thanks though! – Asher Commented Mar 11, 2021 at 13:51
3 Answers
Reset to default 5I’m highly remending doing this on server side for security reasons.Because after build process for your react project in node environment will be executed, all your environment variables would be directly set as a strings on your client, where everybody can access them(accessKeyId and secretAccessKey).
The client(browser or any other environment) doen't have the access to your server's environment, and that's why you're passing those variables at the process of build.
The server, in other hand, has direct access to the machine, where it's running, that's why using environment variables on server side in secure.
For anyone else who may want to do this in future directly from the client side, I was able to implement this by using Blob to convert the data into a Blob URL and then running the function when Download is clicked.
My InputDownload file from above now looks like this:
import React, { useState } from 'react';
import { ListGroup, Dropdown } from 'react-bootstrap';
import AWS from 'aws-sdk';
const InputDownload = () => {
const [template, setTemplate] = useState('Choose Template');
AWS.config.update({
accessKeyId: process.env.REACT_APP_ACCESS_ID,
secretAccessKey: process.env.REACT_APP_ACCESS_KEY,
});
const handleClick = (e) => {
e.preventDefault();
};
const handleDownload = () => {
const s3 = new AWS.S3();
const params = {
Bucket: process.env.REACT_APP_INTERNAL_BUCKET_NAME,
Key: `templates/${template}`,
};
function downloadBlob(blob, name = `${template}.csv`) {
// Convert your blob into a Blob URL (a special url that points to an object in the browser's memory)
const blobUrl = URL.createObjectURL(blob);
// Create a link element
const link = document.createElement('a');
// Set link's href to point to the Blob URL
link.href = blobUrl;
link.download = name;
// Append link to the body
document.body.appendChild(link);
// Dispatch click event on the link
// This is necessary as link.click() does not work on the latest firefox
link.dispatchEvent(
new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window,
})
);
// Remove link from body
document.body.removeChild(link);
}
s3.getObject(params, (err, data) => {
if (err) {
console.log(err, err.stack);
} else {
let csvBlob = new Blob([data.Body.toString()], {
type: 'text/csv;charset=utf-8;',
});
downloadBlob(csvBlob, `${template}`);
}
});
}
return (
<>
<form className='bg-white my-4' onSubmit={handleClick}>
<Dropdown>
<Dropdown.Toggle variant='secondary' id='dropdown-basic'>
{template}
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item onSelect={() => setTemplate('T1')}>
T1</Dropdown.Item>
<Dropdown.Item onSelect={() => setTemplate('IV1')}>
IV1
</Dropdown.Item>
<Dropdown.Item onSelect={() => setTemplate('IV2')}>
IV2
</Dropdown.Item>
<Dropdown.Item onSelect={() => setTemplate('DV1')}>
DV1
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<input
type='submit'
value='Download'
className='btn btn-primary btn-block mt-3'
onClick={handleDownload}
/>
</form>
</>
);
};
export default InputDownload;
You can do it like that but you may use Axios in ReactJS
new Observable((observer) => {
var xhr = new XMLHttpRequest();
xhr.open("get", fileURL, true);
xhr.responseType = "blob";
xhr.onload = function () {
if (xhr.readyState === 4) {
observer.next(xhr.response);
observer.plete();
}
};
xhr.send();
}).subscribe((blob: any) => {
let link = document.createElement("a");
link.href = window.URL.createObjectURL(blob);
link.download = elem.material.driverUrl;
link.click();
});