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.
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
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
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;
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;
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.
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);
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
- Open firebase in your browser and click on 'Get started'
- Create a new project by selecting 'Add Project'
- 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)
- Continue and now we need to create a web app by choosing web
Name your 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;
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;
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.
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;
When you click on any image on the UI, it'll be enlarged and displayed as a modal on our UI.
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;
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.
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.
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
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!
Top comments (0)