Work Snippets

Setup firebase with firestore data in NextJS

Install Required Packages

npm install firebase @radix-ui/react-popover react-quill lucide-react date-fns framer-motion

Set up Firebase

Create a file called firebase.js in a folder called lib in the root of your project and add your Firebase configuration:

import { initializeApp } from "firebase/app"
import { getFirestore } from "firebase/firestore"
 
const firebaseConfig = {
  apiKey: "your-api-key",
  authDomain: "your-auth-domain",
  projectId: "your-project-id",
  storageBucket: "your-storage-bucket",
  messagingSenderId: "your-messaging-sender-id",
  appId: "your-app-id",
}
 
const app = initializeApp(firebaseConfig)
const db = getFirestore(app)
 
export { db }

Create the Component

Create a new component called NewItem.tsx in the components folder:

'use client'
import React, { useState } from "react"
import { addDoc, collection, serverTimestamp } from "firebase/firestore"
import { Popover, PopoverContent, PopoverTrigger } from "@radix-ui/react-popover"
import { format } from "date-fns"
import { motion } from "framer-motion"
import { db } from "@/lib/firebase"
 
interface NewItemProps {
  content?: string
}
 
export function NewItem({ content }: NewItemProps) {
  const [open, setOpen] = useState(false)
  const [title, setTitle] = useState("")
  const [date, setDate] = useState<Date | null>(null)
  const [description, setDescription] = useState("")
  const [label, setLabel] = useState("")
  const [items, setItems] = useState<Item[]>([])
  const [loading, setLoading] = useState(false)
  const [markdownContent, setMarkdownContent] = useState("")
 
  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged((user) => {
      if (user) {
        console.log("user", user)
      }
      setLoading(false)
    })
    return (): void => unsubscribe()
  }, [])
 
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
 
    try {
      const newItem: Item = {
        title,
        userId: user.uid,
        description: markdownContent,
        createdAt: serverTimestamp(),
        id: "",
        subject,
        selectedDate: date,
        label,
      }
 
      const docRef = await addDoc(collection(db, "items"), newItem)
      newItem.id = docRef.id
 
      setItems((prevItems: Item[]) => [newItem, ...prevItems])
      setDescription("")
      setTitle("")
      setDate(null)
      setSubject("")
      setLabel("")
      setMarkdownContent("")
      toast({
        title: "Item created successfully.",
        description: `with title ${title}`,
      })
    } catch (error) {
      toast({
        title: "Something went wrong.",
        description: `Your request failed. Please try again. ${error}`,
        variant: "destructive",
      })
      console.error(error)
    } finally {
      setOpen(false)
    }
  }
 
  // Render form and other JSX here...
 
  return (
    <>
      {/* Render Drawer and other JSX here... */}
    </>
  )
}

In this component:

  • We have used the useState hook to manage the local state for title, date, description, label, and items.
  • The handleSubmit function is used to post the data to Firestore. It creates a new item object, posts it to Firestore, and then adds it to the local state.
  • The Popover component from @radix-ui/react-popover is used to create a dropdown for selecting a date and a label.
  • The ReactQuill component is used for the description input field to allow markdown input.
  • The Drawer component from vaul is used to toggle the visibility of the form.

Fetching the Data

To fetch the data from Firestore and display it, you can create a separate component or use the useEffect hook in the NewItem component or another parent component.

In the useEffect hook, you can use the onSnapshot function from firebase/firestore to listen for real-time updates from Firestore and update the local state accordingly.

useEffect(() => {
  const unsubscribe = onSnapshot(collection(db, "items"), (snapshot) => {
    const fetchedItems: Item[] = []
    snapshot.forEach((doc) => {
      const item = doc.data() as Item
      item.id = doc.id
      fetchedItems.push(item)
    })
    setItems(fetchedItems)
  })
  return (): void => unsubscribe()
}, [])