React Hooks library for Firestore, built using the Firebase v9 modular SDK. It utilizes the useSWRSubscription
function from SWR library to enable subscription-based data fetching and caching.
Inspired by swr-firestore-v9
# if you use NPM
npm i --save @tatsuokaniwa/swr-firestore
# if you use Yarn
yarn add @tatsuokaniwa/swr-firestore
# if you use pnpm
pnpm i @tatsuokaniwa/swr-firestore
import { useCollection, useCollectionCount } from "@tatsuokaniwa/swr-firestore";
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
initializeApp();
const db = getFirestore();
type Post = {
content: string;
status: "draft" | "published";
createdAt: Date;
};
export default function App() {
// Conditional Fetching
const [isLogin, setIsLogin] = useState(false);
const { data } = useCollection<Post>(
isLogin
? {
path: "posts",
where: [["status", "==", "published"]],
orderBy: [["createdAt", "desc"]],
parseDates: ["createdAt"],
}
: null
);
const { data: postCount } = useCollectionCount<Post>({
path: "posts",
where: [["status", "==", "published"]],
});
return (
<div>
<h1>{postCount} posts</h1>
{data?.map((x, i) => (
<div key={i}>
{x.content} {x.createdAt.toLocaleString()}
</div>
))}
<button onClick={() => setIsLogin(!isLogin)}>Toggle auth</button>
</div>
);
}
To perform complex queries like using OR
queries or raw QueryConstraint
, use the queryConstraints
parameter.
However, this method does not provide input completion for field names from type definitions.
import { or, orderBy, where } from "firebase/firestore";
useCollection<City>({
path: "cities",
queryConstraints: [
or(where("capital", "==", true), where("population", ">=", 1000000)),
orderBy("createdAt", "desc"),
],
});
You can use the server module to get the SWR key and data.
import { useCollection, useGetDocs } from "@tatsuokaniwa/swr-firestore"
import { getCollection } from "@tatsuokaniwa/swr-firestore/server"
export async function getStaticProps() {
const params = {
path: "posts",
where: [["status", "==", "published"]],
}
const { key, data } = await getCollection<Post>({
...params,
isSubscription: true, // Add the prefix `$sub$` to the SWR key
})
const { key: useGetDocsKey, data: useGetDocsData } = await getCollection<Post>(params)
return {
props: {
fallback: {
[key]: data,
[useGetDocsKey]: useGetDocsData,
}
}
}
}
export default function Page({ fallback }) {
const { data } = useCollection<Post>({
path: "posts",
where: [["status", "==", "published"]],
})
const { data: useGetDocsData } = useGetDocs<Post>({
path: "posts",
where: [["status", "==", "published"]],
})
return (
<SWRConfig value={{ fallback }}>
{data?.map((x, i) => <div key={i}>{x.content}}</div>)}
</SWRConfig>
)
}
import {
useCollection, // Subscription for collection
useCollectionCount, // Wrapper for getCountFromServer for collection
useCollectionGroup, // Subscription for collectionGroup
useCollectionGroupCount, // Wrapper for getCountFromServer for collectionGroup
useDoc, // Subscription for document
useGetDocs, // Fetch documents with firestore's getDocs
useGetDoc, // Fetch document with firestore's getDoc
} from "@tatsuokaniwa/swr-firestore";
import {
getCollection, // Get the SWR key and data for useCollection, useGetDocs
getCollectionCount, // for useCollectionCount
getCollectionGroup, // for useCollectionGroup, useGetDocs
getCollectionGroupCount, // for useCollectionGroupCount
getDoc, // for useDoc, useGetDoc
} from "@tatsuokaniwa/swr-firestore/server";
import type {
endAt,
endBefore,
limit,
orderBy,
startAfter,
startAt,
where,
} from "firebase/firestore";
type DocumentId = "id";
// First argument of hook, specifies options to firestore, and is also used as a key for SWR.
type KeyParams<T> =
| {
// The path to the collection or document of Firestore.
path: string;
// `Paths` means object's property path, including nested object
where?: [
Paths<T> | DocumentId, // "id" is internally converted to documentId()
Parameters<typeof where>[1],
ValueOf<T> | unknown
][];
orderBy?: [Paths<T> | DocumentId, Parameters<typeof orderBy>[1]][];
startAt?: Parameters<typeof startAt>;
startAfter?: Parameters<typeof startAfter>;
endAt?: Parameters<typeof endAt>;
endBefore?: Parameters<typeof endBefore>;
limit?: number;
limitToLast?: number;
// Array of field names that should be parsed as dates.
parseDates?: Paths<T>[];
}
// OR for more complex query
| {
// The path to the collection or document of Firestore.
path: string;
// raw query constraints from `firebase/firestore`
queryConstraints?:
| [QueryCompositeFilterConstraint, ...Array<QueryNonFilterConstraint>]
| QueryConstraint[];
// Array of field names that should be parsed as dates.
parseDates?: Paths<T>[];
};
import type { QueryDocumentSnapshot } from "firebase/firestore";
type DocumentData<T> = T & Pick<QueryDocumentSnapshot, "exists" | "id" | "ref">;
Subscription for collection
params
: KeyParams | nullswrOptions
: Options for SWR hook exceptfetcher
data
: data for given path's collectionerror
: FirestoreError
import { useCollection } from "@tatsuokaniwa/swr-firestore";
const { data, error } = useCollection<Post>({
path: "posts",
});
Wrapper for getCountFromServer for collection
params
: KeyParams exceptparseDates
| nullswrOptions
: Options for SWR hook exceptfetcher
Returns SWRResponse
data
: number for given path's collection count resulterror
: FirestoreErrorisLoading
: if there's an ongoing request and no "loaded data". Fallback data and previous data are not considered "loaded data"isValidating
: if there's a request or revalidation loadingmutate(data?, options?)
: function to mutate the cached data (details)
import { useCollectionCount } from "@tatsuokaniwa/swr-firestore";
const {
data: postCount,
error,
isLoading,
} = useCollectionCount<Post>({
path: "posts",
});
Subscription for collectionGroup
params
: KeyParams | nullswrOptions
: Options for SWR hook exceptfetcher
data
: data for given path's collectionGrouperror
: FirestoreError
Wrapper for getCountFromServer for collectionGroup
params
: KeyParams exceptparseDates
| nullswrOptions
: Options for SWR hook exceptfetcher
Returns SWRResponse
data
: number for given path's collectionGroup count resulterror
: FirestoreErrorisLoading
: if there's an ongoing request and no "loaded data". Fallback data and previous data are not considered "loaded data"isValidating
: if there's a request or revalidation loadingmutate(data?, options?)
: function to mutate the cached data (details)
Subscription for document
params
: KeyParams exceptwhere
,orderBy
,limit
| nullswrOptions
: Options for SWR hook exceptfetcher
data
: data for given path's documenterror
: FirestoreError
import { useDoc } from "@tatsuokaniwa/swr-firestore";
const { data, error } = useDoc<Post>({
path: `posts/${postId}`,
});
Fetch documents with firestore's getDocs function
-
params
: KeyParams & { useOfflineCache?: boolean; isCollectionGroup?: boolean } | nullset
isCollectionGroup: true
to get data from collectionGroup -
swrOptions
: Options for SWR hook exceptfetcher
Returns SWRResponse
data
: data for given path's collectionerror
: FirestoreErrorisLoading
: if there's an ongoing request and no "loaded data". Fallback data and previous data are not considered "loaded data"isValidating
: if there's a request or revalidation loadingmutate(data?, options?)
: function to mutate the cached data (details)
import { useGetDocs } from "@tatsuokaniwa/swr-firestore";
const { data, error } = useGetDocs<Post>({
path: `posts`,
});
// for collectionGroup
const { data, error } = useGetDocs<Comment>({
path: `comments`,
isCollectionGroup: true,
});
Fetch the document with firestore's getDoc function
params
: (KeyParams & { useOfflineCache?: boolean }) exceptwhere
,orderBy
,limit
| nullswrOptions
: Options for SWR hook exceptfetcher
Returns SWRResponse
data
: data for given path's documenterror
: FirestoreErrorisLoading
: if there's an ongoing request and no "loaded data". Fallback data and previous data are not considered "loaded data"isValidating
: if there's a request or revalidation loadingmutate(data?, options?)
: function to mutate the cached data (details)
import { useGetDoc } from "@tatsuokaniwa/swr-firestore";
const { data, error } = useGetDoc<Post>({
path: `posts/${postId}`,
});
Fetch documents using the Firebase Admin SDK and return the SWR key and data
params
: KeyParams
Returns Promise<{ key: string; data: DocumentData<T>[]; }>
key
: SWR Keydata
: documents in the collection for the given path
import { getCollection } from "@tatsuokaniwa/swr-firestore/server";
// For useCollection
const { key, data } = await getCollection<Post>({
path: "posts",
isSubscription: true, // Add the prefix `$sub$` to the SWR key
});
// For useGetDocs
const { key, data } = await getCollection<Post>({ path: "posts" });
Fetch document's count using the Firebase Admin SDK and return the SWR key and data
params
: KeyParams exceptparseDates
Returns Promise<{ key: string; data: number; }>
key
: SWR Keydata
: number of documents in the collection for the given path.
import { getCollection } from "@tatsuokaniwa/swr-firestore/server";
// For useCollectionCount
const { key, data } = await getCollectionCount<Post>({ path: "posts" });
Fetch documents using the Firebase Admin SDK and return the SWR key and data
params
: KeyParams
Returns Promise<{ key: string; data: DocumentData<T>[]; }>
key
: SWR Keydata
: documents in the collectionGroup for the given path
import { getCollectionGroup } from "@tatsuokaniwa/swr-firestore/server";
// For useCollectionGroup
const { key, data } = await getCollectionGroup<Comment>({
path: "comments",
isSubscription: true, // Add the prefix `$sub$` to the SWR key
});
// For useGetDocs with isCollectionGroup
const { key, data } = await getCollectionGroup<Comment>({ path: "comments" });
Fetch document's count using the Firebase Admin SDK and return the SWR key and data
params
: KeyParams exceptparseDates
Returns Promise<{ key: string; data: number; }>
key
: SWR Keydata
: number of documents in the collection group for the given path
import { getCollectionGroupCount } from "@tatsuokaniwa/swr-firestore/server";
// For useCollectionGroupCount
const { key, data } = await getCollectionGroupCount<Comment>({
path: "comments",
});
Fetch the document using the Firebase Admin SDK and return the SWR key and data
params
: KeyParams
Returns Promise<{ key: string; data: DocumentData<T>; }>
key
: SWR Keydata
: data for given path's document
import { getDoc } from "@tatsuokaniwa/swr-firestore/server";
// For useDoc
const { key, data } = await getDoc<Post>({
path: `posts/${postId}`,
isSubscription: true, // Add the prefix `$sub$` to the SWR key
});
// For useGetDoc
const { key, data } = await getDoc<Post>({ path: `posts/${postId}` });
Before running the test, you need to install the Firebase tools.
npm run test:ci
MIT