Uploading files to Google Cloud Storage
Ting Chung
Posted on February 3, 2021
While building an art portfolio web application, I was tasked with uploading images to Google Firebase Cloud Storage. This was essential to the web application. After all, what is art without pictures? Firebase Cloud Storage helps a user easily upload their pictures.
For my structure, I used Firebase's Realtime database to store the location of the main image. When my user selected an image, it would render several pictures with react-responsive-carousel. In future posts, I will go over how to setup the responsive carousel along with lazy image loading. For this post, I will focus on only the action of uploading the Google Firebase Cloud Storage. The following is the way my Realtime database is structured.
I have a key value pair for images. This images key contains a URL and alt for my main image. With that said, when I render an art image, it reads from this location the Google Firebase URL for this particular image. Also, in order to put the file in its respective folder, it is required to have an "Art route" which is used as a folder name that matches the route(or slug) name.
In order to perform the upload from the client, I used react-bootstrap to put together the form. Here is the code for the form:
<Form onSubmit={handleSubmit}>
<Form.Label>Title</Form.Label>
<Form.Control onChange={handleTitleChange} value={title} type="text"></Form.Control>
<Form.Label>Art route (no spaces)</Form.Label>
<Form.Control onChange={handleArtRoute} value={artRoute} type="text"></Form.Control>
<Form.Label>Description</Form.Label>
<Form.Control onChange={handleDescriptionChange} value={description} type="text"></Form.Control>
<Form.Label>Price</Form.Label>
<Form.Control onChange={handlePriceChange} value={price} type="text"></Form.Control>
<Form.Label>Paypal Price</Form.Label>
<Form.Control onChange={handlePaypalPrice} value={paypalPrice} type="text"></Form.Control>
<Form.Label>Art ID</Form.Label>
<Form.Control ref={artIdRef} type="text" defaultValue={maxId + 1}></Form.Control>
<FormFileInput onChange={handleFileChange} multiple></FormFileInput>
<br></br>
<Button type="submit">Add art</Button>
</Form>
After the user fills in the required fields, I used the FormFileInput component from react-bootstrap to handle uploading the file. When the file is added from a directory, I call on handleFileChange. This is where I call of the function to perform the upload task.
import firebase from "firebase/app";
import imageCompression from 'browser-image-compression';
// these imports were added
const handleFileChange = async (event) => {
event.preventDefault()
const imageFile = event.target.files[0] //store the file in a variable.
const fileRef = firebase.storage().ref('images/' + artRoute).child(imageFile.name) //create a storage reference
const options = {
maxSizeMB: 1,
maxWidthOrHeight: 1920,
useWebWorker: true
}
const compressedFile = await imageCompression(imageFile, options); //perform file compression
let uploadTask = fileRef.put(compressedFile)
uploadTask.on('state_changed', (snapshot) => {
setUploadPercentage(parseFloat(((snapshot.bytesTransferred / snapshot.totalBytes) * 100).toFixed(2)))
}, (error) => {
console.log(error)
},
() => {
setUploadStatus('success')
})
}
I will break down the code line by line. To create the upload, a storage reference needs to be created.
const imageFile = event.target.files[0]
const fileRef = firebase.storage().ref('images/' + artRoute).child(imageFile.name)
When the event of selecting the file is triggered, then stored in imageFile, I create a storage reference containing the folder (artRoute) and create a child that is the name from imageFile. After I have the image file, I want to first compress the image so that it can be read easily in the browser as shown below:
const options = {
maxSizeMB: 1,
maxWidthOrHeight: 1920,
useWebWorker: true
}
const compressedFile = await imageCompression(imageFile, options);
For browser optimization, I compress the file because sometimes I might have a 6MB file that would take a while for the browser to download. I used browser-image-compression to perform the compression. Since the compression is handle asynchronously, I need to use "await" and when the compression is completed, I want to store the compressed file in "compressedFile". Now that I have the compressed file, I want to create an upload task that will perform the upload.
const [uploadStatus, setUploadStatus] = useState('info')
const [uploadPercentage, setUploadPercentage] = useState(0)
//create a state for upload status and percentage
let uploadTask = fileRef.put(compressedFile)
uploadTask.on('state_changed', (snapshot) => {
setUploadPercentage(parseFloat(((snapshot.bytesTransferred / snapshot.totalBytes) * 100).toFixed(2)))
}, (error) => {
console.log(error)
},
() => {
setUploadStatus('success')
})
This code creates an upload task that uploads the file, tracks the upload progress, shows an error if there is one, and allows you to create a function that works like a promise after the upload is completed.
For my example, I used a progress bar that starts grey with no upload, then turns blue for the upload progress, then when complete shows green. The status is tracked with a progress bar created by react-bootstrap. Here is the code below for the progress bar:
<ProgressBar variant={uploadStatus} now={uploadPercentage} label={`${uploadPercentage}%`} />
In this react bootstrap component, variant allows you to choose a color indicator for the status. In this example, I used 'info'(blue) and 'success'(green). The "now" prop will show the visual progress. The label will simply show the percentage number as the progress is being made.
So going back to the code for the upload task, as the state is changed, Firebase Storage will communicate the upload percentage by continuously getting the snapshot. I make sure that the percentage only shows 2 decimal places.
uploadTask.on('state_changed', (snapshot) => {
setUploadPercentage(parseFloat(((snapshot.bytesTransferred / snapshot.totalBytes) * 100).toFixed(2)))
}, (error) => {
console.log(error)
},
() => {
setUploadStatus('success')
})
If there is an error in the upload, I console log the error. If the file upload is complete, I set the upload status to 'success' to show the user it has been completed.
After the upload is completed, I need to get the url that was created and store it in the database:
const [artImageUrl, setArtImageUrl] = useState('')
const [artImageAlt, setArtImageAlt] = useState('')
//Created this state above to track the image url and alt text.
const url = await fileRef.getDownloadURL()
setArtImageAlt(imageFile.name)
setArtImageUrl(url)
The url is now stored in my artImageUrl state which is then fed into my Realtime database for storage.
Thanks for reading and happy coding!
Posted on February 3, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.