- Setup
- Auth
- Cloud Firestore
- Cloud Functions
- Realtime Database
- Cloud Storage for Firebase
- Remote Config
- Performance Monitoring
- Log Page Views to Google Analytics for Firebase with React Router
- Advanced: Using RxJS observables to combine multiple data sources
Since ReactFire uses React's Context API, any child of a FirebaseAppProvider
can call useFirebaseApp()
to get your initialized app. Plus, all ReactFire hooks will automatically check context to see if a firebase app is available.
// ** INDEX.JS **
const firebaseConfig = {
/* add your config object from the Firebase console */
};
render(
<FirebaseAppProvider firebaseConfig={firebaseConfig}>
<MyComponent />
</FirebaseAppProvider>
);
// ** MyComponent.JS **
function MyComponent(props) {
// useFirestore will get the firebase app from Context!
const app = useFirebaseApp();
}
Just as FirebaseAppProvider
allows child components to access the FirebaseApp
instance, each Firebase product SDK (like firebase/auth
or firebase/database
) has a provider:
import { getAuth } from 'firebase/auth'; // Firebase v9+
import { getDatabase } from 'firebase/database'; // Firebase v9+
import { FirebaseAppProvider, DatabaseProvider, AuthProvider, useFirebaseApp } from 'reactfire';
function FirebaseComponents({ children }) {
const app = useFirebaseApp(); // a parent component contains a `FirebaseAppProvider`
// initialize Database and Auth with the normal Firebase SDK functions
const database = getDatabase(app);
const auth = getAuth(app);
// any child components will be able to use `useUser`, `useDatabaseObjectData`, etc
return (
<AuthProvider sdk={auth}>
<DatabaseProvider sdk={database}>
<MyCoolAppThatUsesAuthAndRealtimeDatabase />
</DatabaseProvider>
</AuthProvider>
);
}
Some products benefit from asynchronous initialization. For that, ReactFire has hooks like useInitFirestore
and useInitRemoteConfig
. Learn more about these in the individual product sections below.
If you don't set up a provider for a Firebase product SDK, you'll receive the following error:
SDK not found. useSdk must be called from within a provider
Connect a product SDK to the emulator before passing it to a provider. For example, to connect to the Auth and Realtime Database emulators:
import { getAuth, connectAuthEmulator } from 'firebase/auth'; // Firebase v9+
import { getDatabase, connectDatabaseEmulator } from 'firebase/database'; // Firebase v9+
import { FirebaseAppProvider, DatabaseProvider, AuthProvider, useFirebaseApp } from 'reactfire';
function FirebaseComponents({ children }) {
const app = useFirebaseApp();
const database = getDatabase(app);
const auth = getAuth(app);
// Check for dev/test mode however your app tracks that.
// `process.env.NODE_ENV` is a common React pattern
if (process.env.NODE_ENV !== 'production') {
// Set up emulators
connectDatabaseEmulator(database, 'localhost', 9000);
connectAuthEmulator(auth, 'http://localhost:9099');
}
return (
<AuthProvider sdk={auth}>
<DatabaseProvider sdk={database}>
<MyCoolAppThatUsesAuthAndRealtimeDatabase />
</DatabaseProvider>
</AuthProvider>
);
}
Learn more about the Local Emulator Suite in the Firebase docs.
App Check helps protect your backend resources from abuse, such as billing fraud and phishing.
import { initializeAppCheck, ReCaptchaV3Provider } from 'firebase/app-check';
import { useFirebaseApp, AppCheckProvider } from 'reactfire';
// Create your reCAPTCHA v3 site key in the
// "Project Settings > App Check" section of the Firebase console
const APP_CHECK_TOKEN = 'abcdefghijklmnopqrstuvwxy-1234567890abcd';
function FirebaseComponents({ children }) {
const app = useFirebaseApp(); // a parent component contains a `FirebaseAppProvider`
const appCheck = initializeAppCheck(app, {
provider: new ReCaptchaV3Provider(APP_CHECK_TOKEN),
isTokenAutoRefreshEnabled: true,
});
// Activate App Check at the top level before any component talks to an App-Check-compatible Firebase service
return (
<AppCheckProvider sdk={appCheck}>
<DatabaseProvider sdk={database}>
<MyCoolApp />
</DatabaseProvider>
</AppCheckProvider>
);
}
See the App Check setup guide in the Firebase docs for more detailed instructions.
The following samples assume that FirebaseAppProvider
and AuthProvider
components exist higher up the component tree (see setup instructions for more detail).
The useUser()
hook returns the currently signed-in user.
function HomePage(props) {
const { status, data: user } = useUser();
if (status === 'loading') {
return <span>loading...</span>;
}
return <h1>Welcome Back, {user.displayName}!</h1>;
}
The useSigninCheck
hook makes it easy to decide whether to hide or show UI elements based on a user's auth state:
function UserFavorites() {
const { status, data: signInCheckResult } = useSigninCheck();
if (status === 'loading') {
return <span>loading...</span>;
}
if (signInCheckResult.signedIn === true) {
return <FavoritesList />;
} else {
return <SignInForm />;
}
}
To check custom claims, pass in a requiredClaims
object or a validateCustomClaims
function.
// pass in an object describing the custom claims a user must have
const { status, data: signInCheckResult } = useSigninCheck({ requiredClaims: { superUser: true } });
// OR
// pass in a custom claims validator function
const { status, data: signInCheckResult } = useSigninCheck({
validateCustomClaims: (userClaims) => {
// custom validation logic...
return {
hasRequiredClaims: !!userClaims.superUser,
};
},
});
You may often find that you need to only initialize Firebase Auth with specific dependencies such as local persistence. In order to do this reactfire
provides a hook called useInitAuth
. This allows you to initialize the authApp correctly by passing in the required configuration.
In the below example you will see that we only pass persistance and no providers. You can add providers later as to avoid loading large iFrame on mobile issue.
const { status, data: auth } = useInitAuth(async (authApp) => {
const auth = initializeAuth(authApp, {
persistence: [indexedDBLocalPersistence, browserLocalPersistence],
});
return auth;
});
You can find out more from the Firebase Docs - Custom Dependencies.
The following samples assume that FirebaseAppProvider
and FirestoreProvider
components exist higher up the component tree (see setup instructions for more detail).
Cloud Firestore supports offline data persistence. However, it can be a bit tricky to enable, because you must call enableIndexedDbPersistence
before any other Firestore functions. ReactFire's useInitFirestore
makes this easy to handle:
import { initializeFirestore, enableIndexedDbPersistence } from 'firebase/firestore';
import { useInitFirestore, FirestoreProvider } from 'reactfire';
function App() {
const { status, data: firestoreInstance } = useInitFirestore(async (firebaseApp) => {
const db = initializeFirestore(firebaseApp, {});
await enableIndexedDbPersistence(db);
return db;
});
// firestore init isn't complete yet
if (status === 'loading') {
return <LoadingSpinner />;
}
// pass the Firestore instance to FirestoreProvider
// now we can be sure that any child of FirestoreProvider
// has a fully initialized Firestore instance with
// indexedDbPersistence enabled
return (
<FirestoreProvider sdk={firestoreInstance}>
<CommentText commentId={commentId} />
<LikeCount commentId={commentId} />
</FirestoreProvider>
);
}
This example subscribes to the count/counter
document, and re-renders whenever the document updates.
function Counter() {
const firestore = useFirestore();
const ref = doc(firestore, 'count', 'counter');
const { status, data: count } = useFirestoreDocData(ref);
if (status === 'loading') {
return <span>loading...</span>;
}
return <span> {count.value} </span>;
}
This example queries the animals
collection, sorts by commonName
, and re-renders whenever the collection updates.
function FavoriteAnimals() {
// set up query
const firestore = useFirestore();
const animalsCollection = collection(firestore, 'animals');
const animalsQuery = query(animalsCollection, orderBy('commonName', 'asc'));
// ReactFire!
const { status, data: animals } = useFirestoreCollectionData(animalsQuery, {
idField: 'id', // this field will be added to the object created from each document
});
if (status === 'loading') {
return <span>loading...</span>;
}
return (
<ul>
{animals.map((animal) => (
<li key={animal.id}>{animal.commonName}</li>
))}
</ul>
);
}
The following samples assume that FirebaseAppProvider
and FunctionsProvider
components exist higher up the component tree (see setup instructions for more detail).
function Calculator() {
const functions = useFunctions();
const remoteCalculator = httpsCallable(functions, 'calculate');
const [calculationResult, setResult] = useState(null);
async function handleButtonClick(firstNumber, secondNumber, operator) {
const remoteCalculatorResponse = await remoteCalculator({ firstNumber, secondNumber, operator });
setResult(remoteCalculatorResponse.data);
}
if (!calculationResult) {
return <button onClick={() => handleButtonClick(1, 2, '+')}>Click me to add 1 + 2</button>;
} else {
return <pre>{calculationResult}</pre>;
}
}
If you want to call a function when a component renders, instead of in response to user interaction, you can use the useCallableFunctionResponse
hook.
function LikeCount({ videoId }) {
const { status, data: likeCount } = useCallableFunctionResponse('countVideoLikes', { data: { videoId: videoId } });
return <span>This video has {status === 'loading' ? '...' : likeCount} likes</span>;
}
The following samples assume that FirebaseAppProvider
and RealtimeDatabaseProvider
components exist higher up the component tree (see setup instructions for more detail).
function Counter() {
const database = useDatabase();
const counterRef = ref(database, 'counter');
const { status, data: count } = useDatabaseObjectData(counterRef);
if (status === 'loading') {
return <span>loading...</span>;
}
return <span> {count} </span>;
}
function AnimalsList() {
const database = useDatabase();
const animalsRef = ref(database, 'animals');
const animalsQuery = query(animalsRef, orderByChild('commonName'));
const { status, data: animals } = useDatabaseListData(animalsQuery, {
idField: 'id',
});
if (status === 'loading') {
return <span>loading...</span>;
}
return (
<ul>
{animals.map((animal) => (
<li key={animal.id}>{animal.commonName}</li>
))}
</ul>
);
}
The following samples assume that FirebaseAppProvider
and StorageProvider
components exist higher up the component tree (see setup instructions for more detail).
function CatImage() {
const storage = useStorage();
const catRef = ref(storage, 'cats/newspaper');
const { status, data: imageURL } = useStorageDownloadURL(catRef);
if (status === 'loading') {
return <span>loading...</span>;
}
return <img src={imageURL} alt="cat reading the newspaper" />;
}
function UploadProgress({ uploadTask, storageRef }) {
const { status, data: uploadProgress } = useStorageTask < UploadTaskSnapshot > (uploadTask, storageRef);
let percentComplete;
if (status === 'loading') {
percentComplete = '0%';
} else {
const { bytesTransferred, totalBytes } = uploadProgress;
percentComplete = Math.round(100 * (bytesTransferred / totalBytes)) + '%';
}
return <span>{percentComplete} uploaded</span>;
}
The following samples assume that FirebaseAppProvider
and RemoteConfigProvider
components exist higher up the component tree (see setup instructions for more detail).
ReactFire's useInitRemoteConfig
hook makes it easy to set up Remote Config:
import { getRemoteConfig, fetchAndActivate } from 'firebase/remote-config';
import { useInitRemoteConfig, RemoteConfigProvider } from 'reactfire';
function App() {
const { status, data: remoteConfigInstance } = useInitRemoteConfig(async (firebaseApp) => {
const remoteConfig = getRemoteConfig(firebaseApp);
remoteConfig.settings = {
minimumFetchIntervalMillis: 10000,
fetchTimeoutMillis: 10000,
};
await fetchAndActivate(remoteConfig);
return remoteConfig;
});
if (status === 'loading') {
return <span>initializing Remote Config...</span>;
}
// Child components of RemoteConfigProvider can be confident that Remote Config is
// fully initialized
return (
<RemoteConfigProvider sdk={remoteConfigInstance}>
<WelcomeMessage />
</RemoteConfigProvider>
);
}
function WelcomeMessage() {
const { status, data: messageValue } = useRemoteConfigString('welcome-experiment');
if (status === 'loading') {
return <span>loading...</span>;
}
return <span>{messageValue}</span>;
}
You can import the firebase/performance
library asynchronously to make sure it doesn't affect your page load times:
import { useInitPerformance } from 'ReactFire';
function App() {
useInitPerformance(async (firebaseApp) => {
const { getPerformance } = await import('firebase/performance');
return getPerformance(firebaseApp);
});
//...
}
import { AnalyticsProvider, useAnalytics } from 'reactfire';
import { Router, Route, Switch } from 'react-router';
import { getAnalytics, logEvent } from 'firebase/analytics';
function MyPageViewLogger({ location }) {
const analytics = useAnalytics();
// By passing `location.pathname` to the second argument of `useEffect`,
// we only log on first render and when the `pathname` changes
useEffect(() => {
logEvent(analytics, 'page_view', { page_location: location.href });
}, [location.href]);
return null;
}
function App() {
const app = useFirebaseApp();
return (
<AnalyticsProvider sdk={getAnalytics(app)}>
<Router>
<Switch>
<Route exact path="/about" component={<AboutPage />} />
<Route component={<NotFoundPage />} />
</Switch>
<MyPageViewLogger />
</Router>
</AnalyticsProvider>
);
}
All ReactFire hooks are powered by useObservable
. By calling useObservable
directly, you can subscribe to any observable in the same manner as the built-in ReactFire hooks. If you use RxFire and useObservable
together, you can accomplish more advanced read patterns (like OR queries in Firestore!).