January 7, 2020
  • All
  • Engineering
  • Tutorials
  • firebase
  • react
  • react hooks
  • Tutorials

Firebase Hooks with Ionic React

Aaron Saunders

This is a guest post from Aaron Saunders. Aaron is a developer and CEO at Clearly Innovative. This is the first of a series of posts Aaron wrote about using Firebase and React hooks with Ionic React.

In this post, I’ll walk through the process of creating a custom hook that uploads a file to Firebase.

Since the focus of the post is the custom hook, I will focus on pieces of code related to the hook, how it is called, and how it is implemented, not the surrounding code. However, the complete source code for the project is provided here.

Setting Up Parent Component

We need to make sure we set things up by initializing the custom file upload hook useFirebaseUpload:

// custom hook that will upload to firebase
import useFirebaseUpload from "../hooks/useFirebaseUpload";


// setting up the hook to upload file and track its progress
const [ { data, isLoading, isError, progress }, setFileData ] = useFirebaseUpload();

Next, in the parent component, we want to present any errors that are generated and get progress information when the file is being uploaded from the custom file upload hook useFirebaseUpload. The following properties are all reactive and provided by the custom hook: isError, isLoading and progress.

<IonContent>
  {/* get error from hook and display if necessary */}
  {isError && <div>ERROR: {isError.message}</div>}

  {/* get loading info from hook & display progress if necessary */}
  {isLoading && progress && (
    <IonProgressBar value={progress.value}></IonProgressBar>
  ) }
</IonContent>

The last missing piece for the parent component is selecting the file and then calling the method on the custom Firebase hook to upload the file. We handle that with the code listed below.

Calling that function will set a property in the hook that is a dependency for the useEffects handler we set that actually triggers the Firebase upload to start.

{/* user selects a file and returns the info required for upload */}
  <input
    type="file"
    onChange={(e: any) => setFileData(e.target.files[0])}
  />

Inside Custom Firebase File Upload Hook

We will initialize Firebase at the start of the component function, and define a reference to the storage to be used throughout the component function.

To set up Firebase, be sure to refer to the Firebase docs.

import { useState, useEffect } from "react";
import firebase from "firebase";

var firebaseConfig = {
// ADD YOUR FIREBASE CONFIGURATION
};

// Initialize Firebase
firebase.initializeApp(firebaseConfig);

// the firebase reference to storage
const storageRef = firebase.storage().ref();

Since we are using typescript, we need to define some interfaces for use in the hook then we define the return type from the hook function

interface UploadDataResponse { metaData: firebase.storage.FullMetadata, downloadUrl: any };
interface ProgressResponse { value: number }

function FirebaseFileUploadApi(): [{
    data: UploadDataResponse | undefined,
    isLoading: boolean,
    isError: any,
    progress: ProgressResponse | null
},
    Function
] { //additional code... }

Next, we start to define the state variables needed by the hook.

// the data from the firebase file upload response
const [data, setData] = useState<UploadDataResponse | undefined>();

// sets properties on the file to be uploaded, this is called
// by the parent component
const [fileData, setFileData] = useState<File | null>();

// if we are loading a file or not
const [isLoading, setIsLoading] = useState<boolean>(false);

// if an error happened during the process
const [isError, setIsError] = useState<any>(false);

// used for tracking the % of upload completed
const [progress, setProgress] = useState<ProgressResponse | null>(null);

The useEffect handler

useEffect is called after every render of the component. You can control the rendering by providing an array of dependencies as the second parameter.

With our hook, we only want it to be called when the fileData property changes. Meaning that the user has selected a file to upload and indicated that by calling the setData method.

// this function will be called when the any properties in the dependency array changes
useEffect(() => {
    const uploadData = async () => {
        // initialize upload information
        setIsError(false);
        setIsLoading(true);

        setProgress({ value: 0 });

        if (!fileData) return;

        // wrap in a try catch block to update the error state
        try {
            let fName = `${(new Date()).getTime()}-${fileData.name}`

            // setting the firebase properties for the file upload
            let ref = storageRef.child("images/" + fName);
            let uploadTask = ref.put(fileData);

            // tracking the state of the upload to assist in updating the
            // application UI
            //
            // method details covered in the next section...
            uploadTask.on(
                firebase.storage.TaskEvent.STATE_CHANGED,
                _progress => { },
                _error => { },
                async () => { }
            );
        } catch (_error) {
            setIsLoading(false);
            setIsError(_error);
        }
    };

    fileData && uploadData();
}, [fileData]);

Manage Firebase File Upload State Changes

The call to upload the file, ref.put(fileData), returns a property that we can use to monitor the state of the upload.
This could be the current upload progress, any errors, or when the upload has completed.
We have included a handler for each one and set the appropriate state variable to be accessible from the hook.
For the completion handler, we’ll have to dig deeper since we need to make another call to Firebase using uploadTask.snapshot.ref.getDownloadURL(). This will return the downloadUrl which is needed to render the image in the application.

// tracking the state of the upload to assist in updating the
// application UI

uploadTask.on(
    firebase.storage.TaskEvent.STATE_CHANGED,
    _progress => {
        var value =
            (_progress.bytesTransferred / _progress.totalBytes);
        console.log("Upload is " + value * 100 + "% done");
        setProgress({ value });
    },
    _error => {
        setIsLoading(false);
        setIsError(_error);
    },
    // completion handler
    async () => {
        setIsError(false);
        setIsLoading(false);

        // need to get the url to download the file
        let downloadUrl = await uploadTask.snapshot.ref.getDownloadURL();

        // set the data when upload has completed
        setData({
            metaData: uploadTask.snapshot.metadata,
            downloadUrl
        });

        // reset progress
        setProgress(null);
    }
);

Wrapping Up

This was a very basic example of how to upload files to Firebase using React Hooks. I have created a separate GitHub repo for this project but have excluded other features such as authentication and account creation. I felt it was important to keep the code simple.

Enjoy! Until next time.


Aaron Saunders