When you can, tell a user why they are waiting and what they are waiting for, if they have to wait. It’s recommended to manage expectations with your UI. Why most apps don’t have this feature is because it is tricky to implement.
How do you know what is happening on the server that is making a fetch request take so long? Unless the server is updating the client via web socket then you are out of luck.
Amplify Storage has a callback that keeps you updated on the upload and download progress of your file. Here's how to set it up.
Setup a Frontend + Amplify Project
Any frontend set up should work but I’m most familiar with React so I’ll be showing you with a React project. But feel free to create a project in any framework; same principles apply.
npx create-react-app monitoring-storage-progress
To configure Amplify on this project, you need to have the Amplify CLI installed.
npm install -g @aws-amplify/cli
On the newly created project, run the init command to setup Amplify
amplify init
Since we intend to work with just Amplify Storage, run the add command to configure the project with AWS S3:
amplify add storage
Install the Amplify frontend library to your React project:
npm install aws-amplify
Configure the library in index.js
before rendering the React tree:
import Amplify from 'aws-amplify';
import awsconfig from './aws-exports';
Amplify.configure(awsconfig);
Amplify adds an aws-exports
file when you set up Amplify in case you were wondering.
Uploading Files
In your src/App.js
add an input element of type file
:
<div>
<div>
<label htmlFor="f">
<input
type="file"
id="f"
onChange={(e) => {
uploadFile(e.target.files[0]);
}}
/>
<div>Pick a File to Upload</div>
</label>
</div>
</div>;
The onChange
method calls uploadFile
and passes it the file that you picked.
Before you implement uploadFile
, import Storage
from the Amplify library in the src/App.js
file. You need the methods it exposes to upload and download files from AWS S3:
import { Storage } from "aws-amplify";
Now create the uploadFile
function in the App
component:
const [key, setKey] = React.useState("");
const uploadFile = async (file) => {
try {
const result = await Storage.put(file.name, file, {
contentType: file.type,
});
console.log(result);
setKey(result.key);
} catch (error) {
console.log(error);
}
};
uploadFile
forwards the file object to Storage.put
which takes the name of the file, the file object and a configuration object. The file name does not have to be the name of the file that you are uploading, you can use any string.
After the upload, Storage.put
returns a key which you can use to identify the file that was uploaded. Kinda like the unique ID for the uploaded file. I’ve set the returned key as the value of key
state because we need the key to download the uploaded file.
Downloading Files
To download a file, call the Storage.get
function and pass it a key to the file you want to download.
Add a button to trigger the download and call a downloadFile
function in the onClick
event handler:
{key && (
<button
onClick={() => {
downloadFile();
}}>
Download
</button>
)}
Add the downloadFile
function in the App
component:
const downloadFile = async () => {
try {
const data = await Storage.get(key, { download: true });
console.log(data);
} catch (error) {
console.log(error);
}
};
Monitoring Upload and Download Progress
The put
and get
methods on the Storage
object take a config object as one of the arguments:
Storage.put(file.name, file, { /* Config */ });
Storage.get(key, { /* Config */ });
You can pass a function called progressCallback
to the object and Amplify will call it with progress data:
const progressCallback = (progress) => {
console.log(`Progress: ${progress.loaded}/${progress.total}`);
};
Storage.put(file.name, file, { progressCallback });
Storage.get(key, { progressCallback });
I took an extra step to convert the progress data to percentage values:
const progressCallback = (progress) => {
const progressInPercentage = Math.round(
(progress.loaded / progress.total) * 100
);
console.log(`Progress: ${progressInPercentage}%`);
};
const uploadFile = async (file) => {
try {
const result = await Storage.put(file.name, file, {
contentType: file.type,
// Progress callback
progressCallback,
});
console.log(result);
setKey(result.key);
} catch (error) {
console.log(error);
}
};
const downloadFile = async () => {
try {
const data = await Storage.get(key, { download: true, progressCallback /*Progress Callback*/ });
console.log(data);
} catch (error) {
console.log(error);
}
};
Conclusion
You can take the demo even one step further and add a progress bar. Progress bars take a current value and a max value. And you have these data already. You can learn more tricks like this form the Storage docs