react-on-fire
v1.0.2
Published
React hooks and helpers for Firebase (Firestore, Auth, Storage, FCM)
Maintainers
Readme
react-on-fire
react-on-fire is a React library for Firebase: Firestore (documents, collections, queries, aggregates, batch ops), Authentication (email, social, phone, anonymous, profile sync with Firestore), Storage (uploads, downloads, folders), Cloud Messaging (web FCM token + foreground messages), plus small utilities.
It is meant to be used inside a React app that already uses Firebase JS SDK v12+.
Repository: github.com/moFUNCTION/React-on-Fire
Requirements
- Node 18+ (recommended for tooling)
- React 18 or 19
- Firebase
^12(installed as a dependency of this package; your app should use a compatible version)
Installation
npm install react-on-fireQuick start
Wrap your app with FirebaseProvider. It initializes the Firebase app, Auth, Firestore, Storage, Realtime Database, Analytics, and nests AuthProvider for user state tied to a Firestore users collection.
import { FirebaseProvider } from "react-on-fire";
import type { FirebaseOptions } from "firebase/app";
const firebaseConfig: FirebaseOptions = {
apiKey: "...",
authDomain: "...",
databaseURL: "...",
projectId: "...",
storageBucket: "...",
messagingSenderId: "...",
appId: "...",
measurementId: "...",
};
export function App() {
return (
<FirebaseProvider
config={firebaseConfig}
usersCollectionName="Users"
>
<MainRoutes />
</FirebaseProvider>
);
}usersCollectionName(optional): Firestore collection for user profile documents (default"Users"). Used by auth/profile flows.
Read Firebase clients anywhere:
import { useConfigProvider, useFirebaseAuth } from "react-on-fire";
function Example() {
const { app, auth, firestore, storage } = useConfigProvider();
const authContext = useFirebaseAuth(); // from AuthProvider
return null;
}Package layout and imports
You can import everything from the root or use subpaths for clearer boundaries and tree-shaking.
| Subpath | Purpose |
|--------|---------|
| react-on-fire | All exports from the main barrel (see index.ts) |
| react-on-fire/provider | FirebaseProvider, AuthProvider, useConfigProvider, useFirebaseAuth |
| react-on-fire/firestore | Firestore classes, hooks, types |
| react-on-fire/auth | useAuth, auth action hooks, types |
| react-on-fire/storage | StorageHandler, UploadFiles, storage hooks, types |
| react-on-fire/messaging | FCM hook + optional Cloud Functions helpers |
| react-on-fire/common | isFirestoreValue, getFirebaseErrorMessage, FirebaseLocale |
| react-on-fire/utilities | useServerTime |
Example — subpaths:
import { useDocument } from "react-on-fire/firestore";
import { useAuth } from "react-on-fire/auth";
import { FirebaseProvider } from "react-on-fire/provider";1. Provider module
FirebaseProvider
Initializes Firebase and exposes useConfigProvider() with:
app,analytics,auth,firestore,google_auth,storage,realtimeDb
AuthProvider / useFirebaseAuth
FirebaseProvider already wraps your app with AuthProvider. useFirebaseAuth() returns the same object as useAuth (UseAuthReturn): user, loading, error, isAuthenticated, and all auth methods — but it reads from React context, so it only works under AuthProvider.
import { useFirebaseAuth } from "react-on-fire";
function ProfileBadge() {
const { user, loading, isAuthenticated } = useFirebaseAuth();
if (loading) return null;
return <span>{isAuthenticated ? user?.displayName ?? user?.email : "Guest"}</span>;
}Prefer useFirebaseAuth in most UI code so everything goes through the provider. Use useAuth({ usersCollectionName }) only if you mount a separate AuthProvider yourself.
2. Firestore module
Firestore code is built around Document and CollectionHandler classes plus many React hooks. Most hooks use useConfigProvider() and require the provider above.
Document operations — useDocumentOperations
Full CRUD and utilities for one document.
import { useDocumentOperations } from "react-on-fire/firestore";
function EditUser({ userId }: { userId: string }) {
const {
getDocument,
updateDocument,
loading,
error,
} = useDocumentOperations({
collectionName: "users",
documentId: userId,
onError: (e) => console.error(e),
});
const save = async () => {
await getDocument();
await updateDocument({ displayName: "Ada" });
};
return (
<button type="button" onClick={save} disabled={loading}>
{error ? String(error) : "Save"}
</button>
);
}Read document — useDocument
One-shot fetch or live subscription.
import { useDocument } from "react-on-fire/firestore";
function ProductCard({ id }: { id: string | null }) {
const { data, loading, error, refetch } = useDocument({
collectionName: "products",
documentId: id,
subscribe: true,
enabled: !!id,
});
if (loading) return <p>Loading…</p>;
if (error) return <p>{error.message}</p>;
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}Realtime snapshot — useDocumentSnapshot
Listener-focused API for a single document (always onSnapshot-style).
import { useDocumentSnapshot } from "react-on-fire/firestore";
function LiveCounter({ chatId }: { chatId: string | null }) {
const { data, loading, error } = useDocumentSnapshot({
collectionName: "chats",
documentId: chatId,
enabled: !!chatId,
});
return <div>{loading ? "…" : data?.messageCount ?? 0}</div>;
}Pagination — useCollectionWithPagination
Cursor-based pages with optional realtime snapshots. Configure filters with whereQueries and sort with orderByQueries (types from CollectionHandler).
import { useCollectionWithPagination } from "react-on-fire/firestore";
function OrderList() {
const {
data,
loading,
error,
page,
pagesCount,
handleGetNextPage,
handleGetPreviousPage,
isDisabledNext,
isDisabledPrev,
handleRender,
count,
} = useCollectionWithPagination({
collectionName: "orders",
pageSize: 20,
orderByQueries: [{ field: "createdAt", direction: "desc" }],
isRealtime: false,
});
return (
<div>
<button type="button" onClick={() => void handleRender()}>Refresh</button>
{error && <p>{error}</p>}
<ul>
{data.map((row) => (
<li key={row.id}>{JSON.stringify(row.data)}</li>
))}
</ul>
<p>
Page {page + 1}
{pagesCount > 0 ? ` / ${pagesCount}` : ""}
{count.count > 0 ? ` — ${count.count} docs` : ""}
</p>
<button type="button" onClick={() => void handleGetPreviousPage()} disabled={isDisabledPrev}>
Previous
</button>
<button type="button" onClick={() => void handleGetNextPage()} disabled={isDisabledNext}>
Next
</button>
</div>
);
}Aggregates — useCollectionAggregates
Pass an aggregations array (AggregationConfig[]) for count / sum / average over the collection, with optional whereQueries and isRealtime.
import { useCollectionAggregates } from "react-on-fire/firestore";
function Stats() {
const { data, loading, error } = useCollectionAggregates({
collectionName: "orders",
aggregations: [
{ type: "count", key: "total" },
{ type: "sum", field: "amount", key: "revenue" },
],
whereQueries: [{ field: "status", operator: "==", value: "paid" }],
isRealtime: false,
});
if (loading) return null;
if (error) return <p>{error.message}</p>;
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}Batch writes — useBatchUpdate, useBatchDelete, useBatchCreate
Each hook is scoped to one collectionName and exposes an async function (batchUpdate, batchDelete, or batchCreate) plus loading / error / reset.
import { useBatchUpdate } from "react-on-fire/firestore";
function BulkDeactivate({ ids }: { ids: string[] }) {
const { batchUpdate, loading } = useBatchUpdate({
collectionName: "users",
});
const run = async () => {
await batchUpdate(
ids.map((docId) => ({ docId, data: { active: false } })),
);
};
return (
<button type="button" onClick={() => void run()} disabled={loading}>
Deactivate all
</button>
);
}Search / query helpers
Hooks such as useSearchByField, useSearchByPrefix, useAdvancedSearch, useGenericQuery, useGetDistinctValues, useExportCollectionAsJSON, etc., wrap CollectionHandler for common query patterns. Import from react-on-fire/firestore and pass collection name + filter options as required by each hook.
Classes — Document, CollectionHandler
Use directly when not inside a hook (e.g. server actions are not applicable here—but usable from non-React modules with a Firestore instance):
import { CollectionHandler, Document } from "react-on-fire/firestore";
import { useConfigProvider } from "react-on-fire/provider";
function LowLevel() {
const { firestore } = useConfigProvider();
const handler = new CollectionHandler(firestore, "items");
return null;
}More detail: see docs/FIRESTORE.md.
3. Auth module
useAuth
Primary hook: auth state plus email/social/phone/anonymous flows, profile and password helpers. Requires Firebase from FirebaseProvider (useConfigProvider is used internally).
import { useAuth } from "react-on-fire/auth";
function LoginPage() {
const auth = useAuth();
if (auth.loading) return <p>Loading…</p>;
const onEmailLogin = async () => {
await auth.loginWithEmail("[email protected]", "secret");
};
const onGoogle = async () => {
await auth.loginWithGoogle({ saveUserGeo: true });
};
return (
<div>
<button type="button" onClick={onEmailLogin}>Email login</button>
<button type="button" onClick={onGoogle}>Google</button>
</div>
);
}Typical methods include: createUserWithEmail, loginWithEmail, loginWithGoogle / Facebook / Twitter / Github, signInAnonymously, phone sendPhoneCode / verifyPhoneCode, logout, updatePassword, sendPasswordReset, updateDocData, linkProvider, and more (see UseAuthReturn in types).
Auth action links (password reset, verify email, recover email)
useAuthActionParams takes a URLSearchParams instance (from the current URL or your router). For reset-password links, call verifyCode when the page loads, then resetPassword(newPassword) when the user submits the form.
import { useEffect, useMemo } from "react";
import {
useAuthActionParams,
useAuthActionResetPassword,
isSupportedAuthActionMode,
} from "react-on-fire/auth";
function ResetPasswordRoute() {
const searchParams = useMemo(
() => new URLSearchParams(window.location.search),
[],
);
const params = useAuthActionParams(searchParams);
const { verifyCode, resetPassword, state, email, error, clearError } =
useAuthActionResetPassword(params.oobCode);
useEffect(() => {
if (params.mode === "resetPassword" && params.oobCode) {
void verifyCode();
}
}, [params.mode, params.oobCode, verifyCode]);
if (!isSupportedAuthActionMode(params.mode)) {
return <p>Invalid or unsupported link</p>;
}
const busy = state === "validating" || state === "submitting";
return (
<form
onSubmit={(e) => {
e.preventDefault();
const pwd = new FormData(e.currentTarget).get("p") as string;
void resetPassword(pwd);
}}
>
{email && <p>Account: {email}</p>}
{error && (
<p>
{error.message_en.message}
<button type="button" onClick={clearError}>Dismiss</button>
</p>
)}
<input name="p" type="password" autoComplete="new-password" />
<button type="submit" disabled={busy || state !== "valid"}>
Update password
</button>
</form>
);
}More detail: docs/AUTH.md.
4. Storage module
Hooks (recommended in components)
import {
useFirebaseStorage,
useStorageHandler,
useUploadFiles,
} from "react-on-fire/storage";
function Uploader() {
const storage = useFirebaseStorage();
const handler = useStorageHandler("uploads");
const uploadFiles = useUploadFiles();
const onFiles = async (files: FileList | null) => {
if (!files?.length) return;
await uploadFiles({ files: Array.from(files), StoragePath: "docs" });
};
return <input type="file" multiple onChange={(e) => void onFiles(e.target.files)} />;
}StorageHandler class
Imperative API for uploads, downloads, listing, delete, metadata, batch ops, etc.
import { StorageHandler } from "react-on-fire/storage";
import { useConfigProvider } from "react-on-fire/provider";
function ManualUpload({ file }: { file: File }) {
const { storage } = useConfigProvider();
const handler = new StorageHandler({ storage, basePath: "public" });
const run = async () => {
const info = await handler.uploadFile("images/hero.png", file);
console.log(info.downloadURL);
};
return <button type="button" onClick={run}>Upload</button>;
}More detail: docs/STORAGE.md.
5. Cloud Messaging module
useFirebaseMessaging
Web FCM: service worker, permission, token, foreground onMessage.
import { useFirebaseMessaging, useConfigProvider } from "react-on-fire";
function FcmRegistrar() {
const { app } = useConfigProvider();
useFirebaseMessaging({
app,
vapidKey: import.meta.env.VITE_FIREBASE_VAPID_KEY,
enabled: true,
serviceWorkerPath: "/firebase-messaging-sw.js",
onForegroundMessage: (payload) => {
console.log("Foreground notification", payload);
},
onToken: async (token) => {
// e.g. persist token in Firestore for targeted sends
console.log(token);
},
});
return null;
}You must host firebase-messaging-sw.js at the origin root and use HTTPS in production.
Callable helpers
callSubscribeFcmTopics, Callables type exports — see docs/FIREBASE_MESSAGING.md and Messaging/callables.ts (wired to your Cloud Functions names).
6. Common module
getFirebaseErrorMessage
Maps Firebase error codes to localized messages (supports Arabic locale helpers — see FirebaseLocale).
import { getFirebaseErrorMessage } from "react-on-fire/common";
try {
// firebase call
} catch (e) {
console.log(getFirebaseErrorMessage(e, "en"));
}isFirestoreValue
Validates values suitable for Firestore writes.
import { isFirestoreValue } from "react-on-fire/common";
if (isFirestoreValue(payload)) {
// safe to pass to Firestore
}7. Utilities
useServerTime
Subscribes to Realtime Database .info/serverTimeOffset (via FirebaseProvider) and returns an estimated Date for the server clock, updated when offset changes.
import { useServerTime } from "react-on-fire/utilities";
function ServerClock() {
const serverTime = useServerTime();
return <time dateTime={serverTime.toISOString()}>{serverTime.toLocaleString()}</time>;
}TypeScript
Types ship with the package (dist/*.d.ts). Enable strict mode in your app for best inference.
Building and publishing (maintainers)
From this folder (react-fire/):
npm install
npm run build
npm publish --access publicprepublishOnlyrunsnpm run buildautomatically before publish.- Published artifacts:
dist/(ESM + declarations), plus README and LICENSE.
Before the first publish, confirm the package name react-on-fire is available on npm or change the "name" field in package.json.
License
ISC — see LICENSE.
