TLDR;
@react-firebase was released to make integrating Firebase in your React App fast & easy ๐.
If you use React & Firebase, go check it out !
The longer version
I โค๏ธ Firebase & React but getting them to work together on relatively large codebases with other contributors tends to lead to :
- ๐ Un-necessary complexity in your codebase
- ๐ Subtle bugs with subscription management and inter-resource dependencies.
- ๐ Zombie listeners sticking around when your UI doesn't need them.
@react-firebase
offers small, typed, well-tested composable components that allow easy integration of Firebase Auth, Realtime Database and/or Firestore in your React or React Native app.
Overview
@react-firebase/auth
, @react-firebase/database
, @react-firebase/firestore
export Provider
and Consumer
Components.
Providers
The Provider
components take in firebase
and firebase config options props and should be rendered once in your app as an ancestor to your Consumer
components.
How do I get my Firebase config ?
Web or React Native with Expo
If you're using firebase
Change PROJECT_NAME
to your project name and grab your firebase config here :
https://console.firebase.google.com/project/PROJECT_NAME/settings/general/
It should look something like this :
// Firebase Config
const config = {
apiKey: "API_KEY",
projectId: "PROJECT_ID",
databaseURL: "DATABASE_URL",
authDomain: "AUTH_DOMAIN",
// OPTIONAL
storageBucket: "STORAGE_BUCKET",
messagingSenderId: "MESSAGING_SENDER_ID"
};
React Native Firebase
If you're using react-native-firebase then you only need to pass in your version of firebase as a prop (the firebase app is already initialized on the native side.)
Provider Examples
import * as firebase from "firebase/app";
import "firebase/auth";
import { FirebaseAuthProvider } from "@react-firebase/auth";
// And or
import { FirebaseDatabaseProvider } from "@react-firebase/database";
import "firebase/database";
// And or
import "firebase/firestore";
import { FirestoreProvider } from "@react-firebase/firestore";
<FirebaseAuthProvider firebase={firebase} {...config}>
<MyApp1 />
<MyApp2 />
</FirebaseAuthProvider>
Consumers
Every Module exports Consumer components that will consume and react (๐ ) to Firebase changes.
All Consumer Components expect their children
prop to be a React Node or a sync function that returns a React Node. (library-specific examples below ๐)
Firebase Auth Consumers
FirebaseAuthConsumer
Anywhere inside your app component tree, add a FirebaseAuthConsumer to listen and update the ui when the user's auth status changes.
<FirebaseAuthConsumer>
{({ isSignedIn, user, providerId }) => {
return isSigned ? (
<pre style={{ height: 300, overflow: "auto" }}>
{JSON.stringify({ isSignedIn, user, providerId }, null, 2)}
</pre>
) : <div>Not signed in.</div>;
}}
</FirebaseAuthConsumer>
Auth API Reference
Firebase Database Consumers
Read Data
FirebaseDatabaseNode
Anywhere inside your app component tree, add a FirebaseDatabaseNode
to listen and update the ui when the data changes at the provided path.
<FirebaseDatabaseNode path={"user_bookmarks/BOOKMARK_ID"}>
{({ value, isLoading }) => {
return (
<pre>
{JSON.stringify({ value, isLoading })}
</pre>
);
}}
</FirebaseDatabaseNode>
You can also query your Firebase data by using all supported Firebase Realtime Database operators :
<FirebaseDatabaseNode path={"user_bookmarks/BOOKMARK_ID"} limitToFirst={10} orderByKey>
{({ value, isLoading }) => {
return (
<pre>
{JSON.stringify({ value, isLoading })}
</pre>
);
}}
</FirebaseDatabaseNode>
Write Data
FirebaseDatabaseMutation
& FirebaseDatabaseTransaction
The mutation API is inspired by apollo-graphql mutations.
The mutation components inject a runMutation
or runTransaction
prop, respectively to their children.
FirebaseDatabaseMutation
, in addition to the path
prop requires a mutation type
prop that can be one of :
"set" | "update" | "push";
FirebaseDatabaseMutation Example.
<FirebaseDatabaseMutation path={collectionPath} type="push">
{({ runMutation }) => (
<button
onClick={async () => {
const { key } = await runMutation(testDocValue);
if (key === null || typeof key === "undefined") return;
this.setState(state => ({
keys: [...state.keys, key]
}));
}}
>
add-node-with-generated-key
</button>
)}
</FirebaseDatabaseMutation>
FirebaseDatabaseTransaction Example.
<FirebaseDatabaseTransaction path={path}>
{({ runTransaction }) => {
return (
<div>
<button
onClick={() => {
runTransaction({
reducer: val => {
if (val === null) {
return 1;
} else {
return val + 1;
}
}
}).then(() => {
console.log("Ran transaction");
});
}}
>
Click me to run a transaction
</button>
</div>
);
}}
</FirebaseDatabaseTransaction>
Firestore Database Consumers
Read Data with FirestoreDocument
& FirestoreCollection
Anywhere inside your app component tree, add a FirestoreDocument
or FirestoreCollection
to listen to the data and update the ui when it changes.
FirestoreDocument
Example.
const s = v => JSON.stringify(v, null, 2);
const App = () => (
<FirestoreDocument path={`/user_bookmarks/id`}>
{({ value, path, isLoading }) => {
return (
<div>
{value && <pre>{s(value)}</pre>}
{path && <div>{path}</div>}
<div>{isLoading}</div>
</div>
);
}}
</FirestoreDocument>
);
FirestoreDocument
API
FirestoreCollection
Example
<FirestoreCollection path="/user_bookmarks/" limit={5}>
{d => {
return <pre>{s(d)}</pre>;
}}
</FirestoreCollection>
FirestoreCollection
API
Write Data with FirestoreMutation
& FirestoreTransaction
& FirestoreBatchedWrite
The mutation API is inspired by apollo-graphql mutations.
The mutation components FirestoreMutation
& FirestoreTransaction
inject a runMutation
or runTransaction
prop, respectively to their children.
FirestoreMutation
in addition to the path
prop requires a mutation type
prop that can be one of :
"set" | "update" | "add";
The BatchedWrite
exposes a different API because it behaves differently than writes and transactions, because it can run many updates before committing
them.
It injects 2 props, addMutationToBatch
and commit
, to its children.
-
addMutationToBatch
type addMutationToBatch = (
{
path,
value,
type
}: {
path: string;
value: any;
type: "add" | "update" | "set" | "delete";
}
) => void;
-
commit
type commit = () => Promise<void>;
FirestoreMutation
Example
<FirestoreProvider firebase={firebase} {...config}>
<FirestoreMutation type="set" path={path}>
{({ runMutation }) => {
return (
<div>
<h2> Mutate state </h2>
<button
onClick={async () => {
const mutationResult = await runMutation({
nowOnCli: Date.now(),
nowOnServer: firebase.firestore.FieldValue.serverTimestamp()
});
console.log("Ran mutation ", mutationResult);
}}
>
Mutate Set
</button>
</div>
);
}}
</FirestoreMutation>
</FirestoreProvider>
FirestoreTranasaction
Example
<FirestoreProvider firebase={firebase} {...config}>
<FirestoreTransaction>
{({ runTransaction }) => {
return (
<div>
<button
data-testid="test-set"
onClick={async () => {
await runTransaction(async ({ get, update }) => {
const res = await get({
path
});
const data = res.data();
if (typeof data === "undefined") return;
await update(path, {
...data,
a: isNaN(parseInt(data.a, 10)) ? 1 : data.a + 1
});
});
}}
>
runTransaction
</button>
</div>
);
}}
</FirestoreTransaction>
</FirestoreProvider>
FirestoreBatchedWrite
Example
<FirestoreBatchedWrite>
{({ addMutationToBatch, commit }) => {
return (
<div>
<h2>Batched write</h2>
<button
onClick={() => {
console.log("adding to batch");
addMutationToBatch({
path,
value: { [`a-value-${Date.now()}`]: Date.now() },
type: "set"
});
}}
>
Add to batch
</button>
<button
onClick={() => {
console.log("committing to batch");
commit().then(() => {
console.log("Committed");
});
}}
>
Commit batch
</button>
</div>
);
}}
</FirestoreBatchedWrite>
Resources
Related Work
- re-base
- redux-firebase
- react-firestore
- Your Library ? Send me a DM or comment here to add it.
Thanks for making it to the end of the post โค๏ธ !
Here are a couple of additional resources just for you :
Top comments (1)
Hi Rakan,
Thanks for the helpful article.
I noticed in this section it should be
return isSignedIn
instead ofisSigned
.