Skip to content

Instantly share code, notes, and snippets.

@brense
Created April 26, 2021 20:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brense/60b1ad9874b0ee9c6d9a77d6568de99e to your computer and use it in GitHub Desktop.
Save brense/60b1ad9874b0ee9c6d9a77d6568de99e to your computer and use it in GitHub Desktop.
some hooks wrapping firebase client lib
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'
import Firebase from 'firebase/app'
import 'firebase/firestore'
const FirebaseContext = React.createContext<{ app: Firebase.app.App }>({ app: {} as unknown as Firebase.app.App })
export function FirebaseProvider({ children, app }: React.PropsWithChildren<{ app: Firebase.app.App }>) {
return <FirebaseContext.Provider value={{ app }}>{children}</FirebaseContext.Provider>
}
export function useFirebase() {
const { app } = useContext(FirebaseContext)
return app
}
export function useFirestore() {
const { app } = useContext(FirebaseContext)
return useMemo(() => app.firestore(), [app])
}
export function useStorage() {
const { app } = useContext(FirebaseContext)
return useMemo(() => app.storage(), [app])
}
export function useAuth() {
const { app } = useContext(FirebaseContext)
return useMemo(() => app.auth(), [app])
}
export function useCollection<T extends {}>(collectionPath: string, queryCallback?: (collection: Firebase.firestore.CollectionReference) => Firebase.firestore.Query | Firebase.firestore.CollectionReference) {
const [{ docs, loading }, setState] = useState<{ loading: boolean, docs: Array<T & { id: string }> }>({ loading: false, docs: [] })
const firestore = useFirestore()
const collection = useMemo(() => firestore.collection(collectionPath), [firestore, collectionPath])
useEffect(() => {
setState({ loading: true, docs: [] })
const snapshot = queryCallback ? queryCallback(collection).get() : collection.get()
snapshot.then(s => {
setState(() => ({ loading: false, docs: s.docs.map(doc => ({ id: doc.id, ...doc.data() as T })) }))
})
}, [collection, queryCallback])
const handleAdd = useCallback(async (data: T, disableStateUpdate: boolean = false) => {
const ref = await collection.add(data)
if (!disableStateUpdate) {
setState(d => ({ loading: d.loading, docs: [{ id: ref.id, ...data }, ...d.docs] }))
}
return ref
}, [collection])
const handleSet = useCallback(async (docId: string, data: T, options?: { batch?: Firebase.firestore.WriteBatch, merge?: boolean }) => {
options?.batch ? options.batch.set(collection.doc(docId), data, { merge: options?.merge || false }) : await collection.doc(docId).set(data, { merge: options?.merge || false })
if (!options || !options.batch) {
setState(d => {
const index = d.docs.findIndex(doc => doc.id === docId)
d.docs[index] = { id: docId, ...data }
return { loading: d.loading, docs: [...d.docs] }
})
}
}, [collection])
const handleUpdate = useCallback(async (docId: string, data: Partial<T>, options?: { batch?: Firebase.firestore.WriteBatch }) => {
options?.batch ? options.batch.update(collection.doc(docId), data) : await collection.doc(docId).update(data)
if (!options || !options.batch) {
setState(d => {
const index = d.docs.findIndex(doc => doc.id === docId)
d.docs[index] = { ...d.docs[index], ...data }
return { loading: d.loading, docs: [...d.docs] }
})
}
}, [collection])
const handleDelete = useCallback(async (docId: string, options?: { batch?: Firebase.firestore.WriteBatch }) => {
options?.batch ? options.batch.delete(collection.doc(docId)) : await collection.doc(docId).delete()
if (!options || !options.batch) {
setState(d => {
const index = d.docs.findIndex(doc => doc.id === docId)
index >= 0 && d.docs.splice(index, 1)
return { loading: d.loading, docs: [...d.docs] }
})
}
}, [collection])
const setDocs = useCallback((docs: Array<T & { id: string }>) => {
setState(s => ({ ...s, docs: [...docs] }))
}, [])
return [docs, setDocs, { loading, add: handleAdd, set: handleSet, update: handleUpdate, delete: handleDelete }] as [typeof docs, typeof setDocs, { loading: boolean, add: typeof handleAdd, set: typeof handleSet, update: typeof handleUpdate, delete: typeof handleDelete }]
}
export function useDoc<T extends {}>(collectionPath: string, docId: string) {
const [{ loading, doc }, setState] = useState({ loading: false, doc: null as null | T })
const firestore = useFirestore()
const docRef = useMemo(() => docId !== '' ? firestore.collection(collectionPath).doc(docId) : null, [firestore, collectionPath, docId])
useEffect(() => {
setState({ loading: true, doc: null })
docRef?.get().then(doc => {
setState({ loading: false, doc: { id: doc.id, ...doc.data() as T } })
})
}, [docRef])
const handleSet = useCallback(async (data: T, options?: { merge?: boolean }) => {
console.log(data)
await docRef?.set(data as any, { merge: options?.merge || false })
setState(s => ({ loading: s.loading, doc: { id: docRef?.id, ...data } }))
}, [docRef])
const handleUpdate = useCallback(async (data: Partial<T>) => {
await docRef?.update(data)
setState(s => ({ loading: s.loading, doc: { ...s.doc, ...data } as T }))
}, [docRef])
return [doc, { loading, set: handleSet, update: handleUpdate }] as [typeof doc, { loading: boolean, set: typeof handleSet, update: typeof handleUpdate }]
}
export function useFirebaseAuthUser(authStateChangedCallback?: (user: Firebase.User | null) => void) {
const [user, setUser] = useState<Firebase.User | null>(null)
const auth = useAuth()
useEffect(() => {
auth.onAuthStateChanged(user => {
setUser(user)
authStateChangedCallback && authStateChangedCallback(user)
})
}, [auth, authStateChangedCallback])
return user
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment