firestore react hooks
npm install @valian/react-firestoreReact hooks for Firebase Firestore with real-time updates and TypeScript support


@valian/react-firestore is a lightweight React hooks library that provides seamless integration with Firebase Firestore. It offers real-time data synchronization, TypeScript support, and comprehensive state management for your React applications.
- 🔄 Real-time updates - Automatic synchronization with Firestore using onSnapshot
- 📘 Full TypeScript support - Type-safe hooks with generic type parameters
- 🎯 Simple API - Easy-to-use hooks that handle loading, error, and data states
- ⚡ Lightweight - Minimal bundle size with zero dependencies
- 🛡️ Error handling - Built-in error management with optional custom error handlers
- 🔧 Flexible - Works with any Firestore query or document reference
``bash`
pnpm add @valian/react-firestore
This library requires the following peer dependencies:
- react ^18 || ^19react-dom
- ^18 || ^19firebase
- ^11 || ^12
Make sure you have Firebase initialized in your project:
`typescript
import { initializeApp } from 'firebase/app'
import { getFirestore } from 'firebase/firestore'
const firebaseConfig = {
// your config
}
const app = initializeApp(firebaseConfig)
export const db = getFirestore(app)
`
The useCollection hook subscribes to a Firestore collection and provides real-time updates.
#### Basic Usage
`typescript
import { useCollection } from '@valian/react-firestore'
import { collection, query, where } from 'firebase/firestore'
import { db } from './firebase'
function TodoList() {
const todosQuery = query(
collection(db, 'todos'),
where('completed', '==', false)
)
const { data, isLoading, hasError } = useCollection({ query: todosQuery })
if (isLoading) return
return (
#### With TypeScript
`typescript
interface Todo {
id: string
title: string
completed: boolean
createdAt: Date
}function TypedTodoList() {
const todosQuery = query(collection(db, 'todos'))
const { data, isLoading, empty, size } = useCollection({
query: todosQuery
})
return (
Todos ({size})
{empty ? (
No todos found
) : (
{data.map((todo) => (
{todo.title} - {todo.completed ? '✓' : '○'}
))}
)}
)
}
`#### With Error Handling
`typescript
function TodoListWithErrorHandling() {
const [errorMessage, setErrorMessage] = useState('') const todosQuery = query(collection(db, 'todos'))
const { data, isLoading, hasError } = useCollection({
query: todosQuery,
onError: (error) => {
console.error('Firestore error:', error)
setErrorMessage('Failed to load todos. Please try again.')
}
})
if (isLoading) return
Loading todos...
if (hasError) return {errorMessage} return (
{data.map((todo, index) => (
- {todo.title}
))}
)
}
`$3
The
useDocument hook subscribes to a single Firestore document.#### Basic Usage
`typescript
import { useDocument } from '@valian/react-firestore'
import { doc } from 'firebase/firestore'
import { db } from './firebase'function UserProfile({ userId }: { userId: string }) {
const userRef = doc(db, 'users', userId)
const { data, isLoading, exists } = useDocument({ ref: userRef })
if (isLoading) return
Loading user...
if (!exists) return User not found return (
{data.name}
{data.email}
)
}
`#### With TypeScript
`typescript
interface User {
name: string
email: string
avatar?: string
createdAt: Date
}function TypedUserProfile({ userId }: { userId: string }) {
const userRef = doc(db, 'users', userId)
const { data, isLoading, exists, hasError } = useDocument({
ref: userRef
})
if (isLoading) return
Loading...
if (hasError) return Error loading user
if (!exists) return User not found return (
{data.avatar &&
}
{data.name}
{data.email}
Member since {data.createdAt.toLocaleDateString()}
)
}
`#### Conditional Document Loading
`typescript
function ConditionalUserProfile({ userId }: { userId?: string }) {
// Pass null when you don't want to subscribe yet
const userRef = userId ? doc(db, 'users', userId) : null
const { data, isLoading, exists, isDisabled } = useDocument({ ref: userRef }) if (isDisabled) return
Please select a user
if (isLoading) return Loading...
if (!exists) return User not found return (
{data.name}
{data.email}
)
}
`API Reference
$3
`typescript
const result = useCollection({
query: Query | null,
onError?: (error: unknown) => void
})
`#### Parameters
-
query: Firestore query object or null to disable the subscription
- onError: Optional error handler function#### Returns
`typescript
{
data: AppModelType[] // Array of documents
snapshot?: QuerySnapshot // Firestore snapshot
isLoading: boolean // True while loading
isDisabled: boolean // True when query is null
hasError: boolean // True if error occurred
empty: boolean // True if collection is empty
size: number // Number of documents
}
`$3
`typescript
const result = useDocument({
ref: DocumentReference | null | undefined,
onError?: (error: unknown) => void
})
`#### Parameters
-
ref: Firestore document reference, null, or undefined to disable
- onError: Optional error handler function#### Returns
`typescript
{
data?: AppModelType // Document data (undefined if loading/error/not exists)
snapshot?: DocumentSnapshot // Firestore snapshot
isLoading: boolean // True while loading
isDisabled: boolean // True when ref is null/undefined
hasError: boolean // True if error occurred
exists?: boolean // True if document exists
}
`Advanced Usage
$3
`typescript
function FilteredTodos({ userId, completed }: { userId: string; completed?: boolean }) {
const query = useMemo(() => {
let q = query(collection(db, 'todos'), where('userId', '==', userId)) if (completed !== undefined) {
q = query(q, where('completed', '==', completed))
}
return q
}, [userId, completed])
const { data, isLoading } = useCollection({ query })
// Component implementation...
}
`$3
`typescript
function TodoApp({ userId }: { userId: string }) {
// Get user info
const userRef = doc(db, 'users', userId)
const user = useDocument({ ref: userRef }) // Get user's todos
const todosQuery = query(
collection(db, 'todos'),
where('userId', '==', userId)
)
const todos = useCollection({ query: todosQuery })
if (user.isLoading || todos.isLoading) {
return
Loading...
} return (
{user.data?.name}'s Todos
)
}
``MIT © Valian
Contributions are welcome! Please feel free to submit a Pull Request.
If you encounter any issues or have questions, please open an issue on GitHub.