Ionic Framework App using React Firebase Hooks - File Upload Hook

Aaron K Saunders - Dec 6 '19 - - Dev Community

Please checkout and subscribe to my video content on YouTube. Feel free to leave comments and suggestions for what content you would like to see.
YouTube Channel

Overview

This is the second in a series of blog posts about Ionic Framework, React Hooks and Firebase.

In this post I am walking through the process of creating a custom hook for uploading a file to firebase.

Since the focus of the post is about the custom hook, I will focus on pieces of code related to the hook, how it is called and how it is implement and not the surrounding code; however the source code for the complete project is provided here. Full Source Code

Setting Up Parent Component

// custom hook that will upload to firebase
import useFirebaseUpload from "../hooks/useFirebaseUpload";
Enter fullscreen mode Exit fullscreen mode

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

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

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>
Enter fullscreen mode Exit fullscreen mode

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]);
    }}
Enter fullscreen mode Exit fullscreen mode

Inside Custom Firebase File Upload Hook

Setting Things Up

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.

Add Firebase to your JavaScript project

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();
Enter fullscreen mode Exit fullscreen mode

Since we are using typescript we need to define some interfaces for use in the hook, and 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... }
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

The useEffect handler

useEffect is called after every render of the component, there is a way to controlling the render 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]);

Enter fullscreen mode Exit fullscreen mode

Manage Firebase File Upload State Changes

The call to upload the file, ref.put(fileData) returns a property that we can used to monitor the state of the upload for errors, for progress updates and for when it completes.

We have included a handler for each one and set the appropriate state variable to be accessible from the hook. We will dig a bit deeper on the completion handler because we need to make another call in to firebase uploadTask.snapshot.ref.getDownloadURL() to get 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);
    },
    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);
    }
);

Enter fullscreen mode Exit fullscreen mode

Wrapping Up

Basic Example

This is a very basic file upload component using firebase. I have created a separate GitHub repo for this project where I have excluded login, create account and other features that you would expect to find. I felt it was important to keep the code simple.

GitHub logo aaronksaunders / simple-file-upload-hook

sample application for ionic react js custom hook for file upload to firebase storage

simple-file-upload-hook

Sample application for ionic react js custom hook for file upload to firebase storage

  • See Firebase Hooks Series On dev.to
  • Ionic Framework App using React Firebase Hooks - File Upload Hook - DEV Community 👩‍💻👨‍💻 https://buff.ly/2OVqB23

Branches

  • See branch with Ionic/Capacitor and hook utilizing Camera from Ionic : with-camera-hook

Video Series






Ionic Custom Hooks & Capacitor Example

As I was wrapping this post, I saw that the team from Ionic had released a blog post about custom hooks Announcing Ionic React Hooks. To see the firebase file upload hook integrated with Ionic Framework and Capacitor, see this branch in the GitHub repo: Integration with Capacitor Custom Hooks

Complete Firebase Hooks Example In React

Sample app integrating firebase with a react application using react hooks api and React Firebase Hooks - A set of reusable react hooks for Firebase. The custom hook developed in this post was enhanced to support additional functionality.

GitHub logo aaronksaunders / react-custom-fb-upload-hooks

sample application for ionic react js custom hook for file upload to firebase storage

Ionic: ReactJS Custom Firebase Storage Hook

This project was bootstrapped with Create React App.

Sample app integrating firebase with a react application using react hooks api and React Firebase Hooks - A set of reusable react hooks for Firebase.

We also have built our own custom firebase storage file upload hook, jump directly to source code

Getting A Collecting of Things

This is from firebase-hooks, it allows us to query all of the item from the things collection in the database in descending order based on the creation data. the value will containg the results of the query that we will loop through to render the list items

const [value, loading, error] = useCollection(
  firebase
    .firestore()
    .collection("image-file")
    .orderBy("createdOn", "desc"),
  {
    snapshotListenOptions: { includeMetadataChanges: true }
  }
)
Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .