DEV Community

Cover image for Firebase React PhotoGallery
Jagadeesh Koyya
Jagadeesh Koyya

Posted on

Firebase React PhotoGallery

Hello there! How do you do? I hope you're extremely well. I'm about to dive you into some mind-blowing project but for doing that I need the next 5 minutes in your life if you'd allow me. That's great!

The idea of this webapp is to build a photo gallery to display our images wherever you go. Yes, the images can stay in there forever. What a nice way to keep all your memories together as images!

You can use it as a mini social media to share some great pictures or moments of your acheivements with your groups and your friends can share some cool stuff too. Sounds fun? Oh, Yeah.

Let's Start Building
You need to install react for creating this project and also the firebase using node js.

  • Let's install react js by creating our project folder named "firegram"

    npx create-react-app firegram
    
  • After that, install firebase using the command given below

    npm i firebase
    
  • We'll be using framer-motion for some cool animations, let's install it and keep it in our project pockets.

    npm i framer-motion
    

Now open the project folder in your favourite code editor (I'd recommend vs code for this project).

Delete the files App.test.js, logo.svg, reportWebVitals.js, setupTests.js as we're not testing for now and clear the code in App.js

The boiler plate looks something like this.

boiler plate

App.js

import ImageGrid from './components/ImageGrid';
import Modal from './components/Modal';
import Title from './components/Title';
import UploadForm from './components/UploadForm';
import { useState } from 'react';


const App = () => {
  const [selectedImg, setSelectedImg] = useState(null);

  return (
    <div className='App'>
        <Title />
        <UploadForm />
        <ImageGrid setSelectedImg = {setSelectedImg} />
        {
          selectedImg && <Modal setSelectedImg = { setSelectedImg }selectedImg = {selectedImg} />
        }
    </div>
  )
}

export default App

Enter fullscreen mode Exit fullscreen mode

This is the root component for our project. Just clean out App.js with the code above and I'm going to demonstrate you the components used in App.js

begin

Title.js

import React from 'react'

const Title = () => {
  return (
    <div className='title'>
      <h1>Photo Gallery</h1>
      <h2>Your Pictures</h2>
      <p>Let the pictures showcase your memories.</p>
    </div>
  )
}

export default Title;
Enter fullscreen mode Exit fullscreen mode

This is the starter file for our project. Pretty much what you see on UI before any images are uploaded.

ImageGrid.js

import { motion } from 'framer-motion';
import useFirestore from '../hooks/useFirestore'

const ImageGrid = ({ setSelectedImg }) => {
  const { docs } = useFirestore('images');
  const unique = [...new Map(docs.map((m) => [m.url, m])).values()];

  return (
    <div className='img-grid'>
      { 
        unique && unique.map(doc => (
          <motion.div 
            className='img-wrap' 
            key = {doc.id} 
            onClick = {() => setSelectedImg(doc.url)}
            whileHover = {{ opacity: 1 }}
            layout
          >
            <motion.img 
              src={doc.url} 
              alt="uploaded-img" 
              initial = {{ opacity: 0 }}
              animate = {{ opacity: 1 }}
              transition = {{ delay: 1 }}
            />
          </motion.div>
        )) 
      }
    </div>
  )
}

export default ImageGrid;
Enter fullscreen mode Exit fullscreen mode

Pretty much what PhotoGrid component does is that it renders each and every image into the UI by collecting the images from firebase firestore database.

firstly

FirebaseConfig.js

import { initializeApp } from "firebase/app";
import { getFirestore } from 'firebase/firestore'; 
import { getStorage } from 'firebase/storage';

const firebaseConfig = {
  apiKey: "",
  authDomain: "",
  projectId: "",
  storageBucket: "",
  messagingSenderId: "",
  appId: ""
};


const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
export const storage = getStorage(app);
Enter fullscreen mode Exit fullscreen mode

This is the linking file to our project and firebase. You need to get the firebaseConfig data by creating a project in firebase. I'll show you how.

Firebase

  1. Open firebase in your browser and click on 'Get started'
  2. Create a new project by selecting 'Add Project'
  3. Name your project and you're good to go. (It's your wish to enable or disable google analytics, for this project it's not needed)

firebase

firebase

firebase

  1. Continue and now we need to create a web app by choosing web

webapp

webapp

Name your webapp.

webapp

Copy the code and save it in your project folder as firebaseConfig.js and the connection is established.

We'll be using the firestore database and storage services of firebase. For that, I've created two hooks for each of them. Let me enlighten you.

useFirestore.js

import { db } from "../firebase/config";
import { useEffect, useState } from 'react';
import { collection, onSnapshot, query, orderBy } from "firebase/firestore";

const useFirestore = (col) => {
  const [docs, setDocs] = useState([]);
  const collectionRef = collection(db, col);
  const q = query(collectionRef, orderBy("createdAt", "desc"));


    useEffect(() => {
        const unsub = onSnapshot(q, (snap) => {
            let documents = [];
            snap.forEach(doc => {
                documents.push({...doc.data(), id: doc.id})
            });
            setDocs(documents);
        })

        return () => unsub(); 

    }, [collection])

  return { docs };
}

export default useFirestore;

Enter fullscreen mode Exit fullscreen mode

We're using the useEffect hook to fetch the docs (images) from the firestore database (db) every time there's a change in the firestore database. Given a query, we pass it as a parameter to onSnapshot, which takes a snapshot of the firestore db every time a change occurs.

useStorage.js

import { useEffect, useState } from 'react';
import { storage, db } from '../firebase/config';
import { 
    ref, 
    uploadBytesResumable, 
    getDownloadURL 
} from "firebase/storage";
import { 
    collection, 
    addDoc, 
    serverTimestamp
  } from 'firebase/firestore';

const useStorage = (file) => {
    const [progress, setProgress] = useState(0);
    const [error, setError] = useState(null);
    const [url, setUrl] = useState(null);

  const collectionRef = collection(db, "images");

    useEffect(() => {
        const storageRef = ref(storage, file.name);
        const uploadTask = uploadBytesResumable(storageRef, file);

        uploadTask.on('state_changed', (snapshot) => {
            let percentage = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
            setProgress(percentage);
        }, (err) => {
            setError(err);
        }, async () => {
            const url = await getDownloadURL(uploadTask.snapshot.ref);
            setUrl(url);
            addDoc(collectionRef, { url, createdAt: serverTimestamp() });
        })

    }, [file]);

  return { progress, url, error }
}

export default useStorage;
Enter fullscreen mode Exit fullscreen mode

Oh, don't be worried by the above code. It's pretty much easy. What this component does is that it'll create a collection in firestore database by uploading the url of the image.

We can get the image url after uploading it to the firebase storage by using uploadTask and getDownloadURL.

progress bar

That progress bar you see above is the upload rate of the image to the firebase storage.

Modal.js

import React from 'react';
import { motion } from 'framer-motion';

const Modal = ({ selectedImg, setSelectedImg }) => {
    const handleClick = (e) => {
        if(e.target.classList.contains('backdrop')) {
            setSelectedImg(null);
        }
    }

  return (
    <motion.div 
      className='backdrop' 
      onClick={handleClick}
      initial = {{ opacity: 0 }}
      animate = {{ opacity: 1 }}
    >
        <motion.img 
          src={selectedImg} 
          alt='modal-img'
          initial = {{ y: "-100vh" }}
          animate = {{ y: 0 }}
        />
    </motion.div>
  )
}

export default Modal;
Enter fullscreen mode Exit fullscreen mode

When you click on any image on the UI, it'll be enlarged and displayed as a modal on our UI.

modal

UploadForm.js

import { useState } from 'react';
import ProgressBar from './ProgressBar';

const UploadForm = () => {
    const [file, setFile] = useState(null);
    const [error, setError] = useState(null);
    const types = ['image/png', 'image/jpeg'];

    const changeHandler = (e) => {
        let selected = e.target.files[0];

        if(selected && types.includes(selected.type)) {
            setFile(selected);
            setError('');
        } else {
            setError('Please choose an image (jpeg or png)');
            setFile(null);
        }
    }

  return (
    <form>
        <label>
            <input 
                type = 'file'
                onChange={changeHandler}
            />
            <span>+</span>
        </label>
        <div className='output'>
            { error && <div className='error'>{ error }</div>}
            { file && <div> {file.name} </div> }
            { file && <ProgressBar file = {file} setFile = {setFile} /> }
        </div>
    </form>
  )
}

export default UploadForm;
Enter fullscreen mode Exit fullscreen mode

The imput tag you see on UI with a "+" is rendered by this component. We accept images of types jpeg or png otherwise it'll display an error.

upload img

That's pretty much up to it. I'll wrap up here. I want to thank Brad Traversy and Net Ninja for making this project possible. It's cool as this is my very first webapp.

The final UI looks like this with loaded images using some cool framer-motion animations.

final

Firebase Hosting

We're going to host our webapp in firebase itself with a few commands.

npm i -g firebase-tools
npm run build
firebase login
firebase init
firebase deploy
Enter fullscreen mode Exit fullscreen mode

The steps are to generate build first, then login using your google account to the firebase and init the firebase by choosing already existing project (here firegram) and deploy.

When you enter "firebase init" you need to choose something like ">() Hosting: Config...firebase...Github" by pressing space button.

Choose your firebase project for your webapp and enter "build" as the public directory.

Cool. See you next week. Keep smiling!

Repo ⚡
Live ⚡

Top comments (0)