How to Retrieve Images from Firebase Storage in React

How to Retrieve Images from Firebase Storage in React

I’ve been trying my hands on a couple of new things lately. I recently worked on a website where my team and I had to use Firebase to store and fetch images. I have to tell you it wasn’t so easy because Firebase didn’t fully specify how to consume the data in React and we had to take account of security. In contrast, most tutorials online don’t mention that aspect (which we can discuss how to do in a later article). I’m used to reading through documentation and following its example to the letter, if I follow its example and it still doesn’t work, then I have to find a workaround.

In this article, we will discuss how to retrieve images from Firebase storage using React with the workaround I found.

Prerequisites

  1. Good knowledge of React

  2. Good understanding of Promises

Steps to Retrieve Images from Firebase in React

1. Create a React app and Install Firebase

Create your React app using the following command:

    npx create-react-app fishes

Change your directory in your terminal to the fishes react app using:

    cd fishes

Install firebase and its tools using:

    npm i firebase

2. Create a Firebase Application

We would have to create a Firebase application in firebase.google.com to retrieve its config data. Head over to its console section and create a new app.

add new project on firebase

After you’ve clicked on “Add project”, you’ll be taken to a screen where you can input your project name. Give it whatever name you’d like. For my project, I’m going to give it “fishes” because that’s the react project name I’m working on. Press the “Continue” button when you’re done naming so we can move to the next step.

You’d be taken to a page where you’re asked if you want Google Analytics enabled for your project, we’re not going to use it here, but it can be useful later on if you want to monitor your project data so click continue.

The final step for creating a project: If you have multiple accounts, you’ll be taken to a place where you have to select which Google Analytics account you want the analytics for your project to be. Select an account and press continue.

After you’ve done this, you would have to wait for Google to create your project. Once it's done, your screen should look a lot like this:

Click the Continue button when you’re ready and voilà! You’ve successfully created a Firebase project.

3. Copy Config Data - Real Meat

After you’ve clicked Continue from the previous step, you’ll be taken to a new screen where you have your project details. You can get the config data by simply clicking on the web icon just below the “Get started by adding Firebase to your app” title.

You’d be taken to a screen where you’d have to register your app with a nickname. I’m going to be super creative with mine because I want it to be amazing. So I named it fishes. 🙂

Once you’ve finished giving it a name, click on the “Register app” button. It will take you to the next step where you have the Firebase config data for your web app. Your Firebase config data should look a lot like this if everything goes well:

Copy the config data and paste it somewhere. Next, click the “Continue to console” button after you’ve done that.

Good job so far! If you’ve done everything correctly up to this point, you should be redirected to the console in the first picture of this step.

4. Allow for Images To Be Read

Oh my gosh. This part, this part and some other ones are not explicitly stated in the documentation and I had to always check what’s wrong and do some extra Google searches whenever I run into errors, just like this one with reading the images. Luckily, I went through this so you don’t have to.👍🏾 Anyway, head to the Storage section. The link to it is on the “Build” dropdown on the sidebar.

Click the “Get started” button.

You’d be taken to a mini page where you’d need to select your storage bucket rules. We still need to change these rules to allow our images to be publicly accessible, but for now, just click Next with its default selected production mode option.

Next, select the region of your cloud storage location. Once you’ve done that, click Done.

Once it’s done creating a cloud storage location for your app, your screen should look like this:

Before you upload anything, click on the Rules tab just beside the Files tab below the Storage title.

Paste these new rules there where you have the code for your storage bucket.

    service firebase.storage {
      match /b/{bucket}/o {
        match /{allPaths=**} {
          allow read, write;
        }
      }
    }

This allows you or anyone to access your images without the need for authentication publicly.

Note: I’m only doing this because I want my images to be publicly accessible and I don’t have to go through an authentication process. If you have to add authentication, the process would be a lot different. FirebaseUI documentation provides a way to authenticate your app and get your images. Check it out for further reading and if you want to learn more about it.

It should look like this if you’ve followed the steps correctly:

You’d be asked to publish the changes you made, do that so all your changes can be saved.

And that’s it! Your images can now be accessed in your web app!

5. Upload Images to Your Firebase App

This is the part where we add images we want to use in our app. Now, for the best app ever created with the best unique name - fishes. We would need some images of some nice fishes. Click on the Files tab and click on the Upload File button to upload your images. Do that and head back here, I’ll be uploading my fish images in the meantime. 😀

Notice how there’s also a folder for AI fishes.👀 It will come in handy soon. Oh! And if you want to create a folder in your Firebase storage, simply click on the folder icon beside the Upload File button and write your file name. Inside the folder, you can upload your images there the same way you uploaded into the root folder that you were on.

6. Paste Your Config Data

Now, back to our web app. Remember the config data we copied in step 3? Yeah, we need it here. Create a file in your src folder and call it firebase.js. Paste your config data there.

Notice I exported the firebaseConfig object that has all the important information to access our images. I’ll paste it here as well just to ensure we’re both on the same page.

    // Import the functions you need from the SDKs you need
    import { initializeApp } from "firebase/app";
    import { getAnalytics } from "firebase/analytics";
    // TODO: Add SDKs for Firebase products that you want to use
    // https://firebase.google.com/docs/web/setup#available-libraries
    // Your web app's Firebase configuration
    // For Firebase JS SDK v7.20.0 and later, measurementId is optional
    const firebaseConfig = {
      apiKey: "your value goes here",
      authDomain: "your value goes here",
      projectId: "your value goes here",
      storageBucket: "your value goes here",
      messagingSenderId: "your value goes here",
      appId: "your value goes here",
      measurementId: "your value goes here"
    };
    // Initialize Firebase
    const app = initializeApp(firebaseConfig);
    // export firebaseConfig
    export {firebaseConfig}

7. Add Async/Await to Almost Everything

In your App.js, import firebaseConfig from firebase.js located in your src folder (basically here App.js is):

    import {firebaseConfig} from './firebase.js';

Next, you’d need to import the following from firebase:

    import { initializeApp } from "firebase/app";
    import { getStorage, ref, listAll, getDownloadURL } from "firebase/storage";

These are firebase utilities that help you initialize the app and get the images you need from your storage. getDownloadURL is especially needed to get a URL you can render in your React app.

Next, import useState and useEffect from React to create our states, and to get our data asynchronously from Firebase:

    import { useEffect, useState } from "react";

Great. We’re almost done (I promise). You would need to create two array states, one for the images in the root folder of your storage bucket and the other for the ones in the AI images folder. We also need to check whether the data has been fetched so create one extra state for that and give it a boolean default value.

    function App() {
      const [realFishes, setRealFishes] = useState([]);
      const [aiFishes, setAIFishes] = useState([]);
      const [dataFetched, setDataFetched] = useState(false);
    ...

Now, this is the part where I post a whole bunch of code and explain it line by line. 🙂 Don’t worry, we’re getting somewhere I promise.

    ...
     useEffect(() => {
        async function fetchData() {
          try {
            initializeApp(firebaseConfig); // Initialize Firebase app
            const storage = getStorage(); // Get Storage instance
            // Fetch data for each type and set state
            await fetchImageData(storage, 'ai-fishes/'); //ai-fishes folder reference
            await fetchImageData(storage, '/'); //root folder reference
            // Set loading to false after all data is fetched
            setDataFetched(true);
          } catch (error) {
            console.error("Error fetching data:", error);
          }
        }
        fetchData();
      }, [dataFetched]);
      const fetchImageData = async (storage, path) => {
        try {
          const storageRef = ref(storage, path);
          const result = await listAll(storageRef);
          const urlPromises = result.items.map((imageRef) => getDownloadURL(imageRef));
          const urls = await Promise.all(urlPromises);
          // Set state based on path
          switch (path) {
            case '/':
              setRealFishes(urls);
              break;
            case 'ai-fishes/':
              setAIFishes(urls);
              break;
            default:
              break;
          }
        } catch (error) {
          console.error("Error fetching images:", error);
        }
      };
    ...

Most storage operations in Firebase use Promises that’s why we have to use Async/Await almost everywhere so let’s break it down:

  1. We create a function called fetchData(). We then initialize our Firebase app using initializeApp function from Firebase in the function. Next, we create a storage instance using getStorage(). That’s why our code is like this:
  try {        
          initializeApp(firebaseConfig); // Initialize Firebase app
          const storage = getStorage(); // Get Storage instance
  ...

Notice how I wrapped everything in a try statement. That’s because I ran into several errors while doing this and I wanted to figure out exactly what went wrong. Plus, it’s a good way to try and catch errors.😏 2. Next, we call await on the fetchImageData() function for the two paths where the images are in our storage. We would go over what fetchImageData() does in a sec.

  ...        
      await fetchImageData(storage, 'ai-fishes/'); //ai-fishes folder reference
      await fetchImageData(storage, '/'); //root folder reference
  ...
  1. Let’s enter into fetchImageData() and discuss it for a bit. So you’d notice that it accepts two parameters, the storage instance and the folder path, and you’d also notice it’s an async/await function.

    That’s because we are dealing with promises here to list all our files and get a URL we can display our image with (downloadable URL), meaning we have to wait till the promise is resolved or rejected or we at least get a response because we are waiting for a response, using it as a normal function would not work. It is also wrapped in a try/catch statement to look out for errors in trying to get the data from storage.

    The second line of code inside the function creates a storage reference using the storage instance and the path to the images supplied:

     const fetchImageData = async (storage, path) => {
         try {
           const storageRef = ref(storage, path);
     ...
    

    That’s not all. We also need to wait for Firebase to list all the files in the path using that storageRef variable and call await on the listall() function, passing in the storageRef. Then we need to map the items in the result to get the downloadable URL from it using the JavaScript map function and getDownloadURL() because it exists as a raw object that we cannot use.

           ...
           const result = await listAll(storageRef);
           const urlPromises = result.items.map((imageRef) => getDownloadURL(imageRef));
           ...
    

    This returns a bunch of urlPromises depending on the amount of images you have in that folder. Once you have received a positive response from Firebase and everything went well, we wait for the promises to be resolved using await Promise.all() on the urlPromises we got. It returns a single array of the fulfilled promises. That’s why we made our default state values to be an array because this step returns an array of fulfilled values, in this case, an array of our downloadable image URLS.

    Next, we assign the respective images to their state array arrays using a switch statement based on the path parameter we took in. If it’s in the / folder, we set the state of realFishes to be the URLs we got from Promise.all(), if it’s the ai-fishes/, we set the state of aiImages to be the URLs we got from Promise.all() for that particular folder. Finally, we close our try statement and add a catch statement at the end to catch any errors encountered.

     ...
         const urls = await Promise.all(urlPromises);
           // Set state based on path
           switch (path) {
             case '/':
               setRealFishes(urls);
               break;
             case 'ai-fishes/':
               setAIFishes(urls);
               break;
             default:
               break;
           }
         } catch (error) {
           console.error("Error fetching images:", error);
         }
     ...
    

    Note: Make sure your image paths match the location in your storage where your images are. e.g ‘ai-fishes/’ matches the name of the folder ‘ai-fishes/’ where I stored images of ai fishes and ‘/’ - the root folder matches the root folder of where I stored images of actual fish.

8. Display Images using array.map()

We’re in the final step! Woohoo! I’m glad you reached this point of this super short tutorial that surely didn’t take me days to figure out. Awesome! Anyway, in this step, we simply map the array values and pass them to an img element. You can also add a loader to your app while you wait for your images to load.

   return (
      <div className="App">
        {
          realFishes.length > 0 ?
            realFishes.map((ele, index) => {
              return (
                <div key={index}>
                  <p>Fish #{index+1}</p>
                  <img 
                  style={{width: "50vw", height: "auto", }} src={ele} alt="" />
                </div>
              )
            }) :
            <p>Loading...</p>
        }
        <h2 style={{width: "100vw"}}>AI FISH</h2>
        {
          aiFishes.map((ele, index) => {
            return (
              <div key={index}>
                <img style={{width: "50vw", height: "auto", }} src={ele} alt=""/>
              </div>
              )
          })

        }
      </div>
    );
  }

Full Code

For those who want to skip all my hard work writing and all my explanations, for those who didn’t want to read through my simple explanations.(Yes, this is emotional blackmail) 😔 Here’s the full code:

github.com/sunkanmii/fishes

For real though, there’s still a bunch you can learn from what’s in this tutorial and all. So scroll back up and enjoy the process. 🙂 I promise it’s fun.

Results

Take a look at my beautiful fishes…and then there’s an imposter in their midst. One created by AI. Can you take a guess which one it is without cheating?

It’s not literally with a title that can help you guess which is the one generated by AI. 😄 That’s it for now! Let me know in the comments section what you think of this or do you have an alternative solution that’s interesting and simple too? Let me know down below. I’d love to see what you have cooked up as well. Thank you for reading and see you in the next article!

References

  1. Firebase List Files from Firebase documentation

  2. MDN Promises