Realtime Database

The @react-query-firebase/database package provides hooks fetching and mutating data on Realtime Database.

Installation

npm i --save @react-query-firebase/database

Usage

To use the hooks, export your Database instance, e.g:

import { initializeApp } from 'firebase/app';
import { getDatabase } from 'firebase/database';

const firebase = initializeApp({
  ...
});

export const database = getDatabase(firebase);

useDatabaseSnapshot

The useDatabaseSnapshot hook can be used to query a database reference and return a DataSnapshot, whilst wrapping around the useQuery hook.

import React from "react";
import { ref } from "firebase/database";
import { useDatabaseSnapshot } from "@react-query-firebase/database";
import { database } from "../firebase";

function Profile({ id }: { id: string }) {
  const dbRef = ref(database, `users/${id}`);
  const query = useDatabaseSnapshot(["user", id], dbRef);

  if (query.isLoading) {
    return <div>Loading...</div>;
  }

  const snapshot = query.data;

  if (!snapshot.exists()) {
    return <div>Please sign-in!</div>;
  }

  return <div>Welcome {snapshot.val().displayName}</div>;
}

To subscribe to value changes on the reference, pass the subscribe option to the hook:

// Subscribe to value changes
const query = useDatabaseSnapshot(["user", id], dbRef, {
  subscribe: true,
});

You can also pass along useQuery hook options, for example to return a specific value from the snapshot and be notified on success:

const query = useDatabaseSnapshot[("user", id], dbRef, {
  select(snapshot) {
    if (snapshot.exists()) {
      return snapshot.val().displayName;
    }

    return null;
  },
  onSuccess(displayName) {
    console.log("Fetched the users display name!", displayName);
  },
});

if (query.isSuccess) {
  console.log("Users display name is: ", query.data);
}

TypeScript users can also provide a return type for the data response:

const query = useDatabaseSnapshot<number | null>(["user", id], dbRef, {
  select(snapshot) {
    if (snapshot.exists()) {
      return snapshot.val().age;
    }

    return null;
  },
});

if (query.isSuccess) {
  // query data will be typed as number | null
  console.log("Users age is: ", query.data);
}

useDatabaseValue

The useDatabaseValue hook can be used to query a database reference and return the contents of the ref. The hook wraps around the useQuery hook.

import React from "react";
import { ref } from "firebase/database";
import { useDatabaseValue } from "@react-query-firebase/database";
import { database } from "../firebase";

function Profile({ id }: { id: string }) {
  const dbRef = ref(database, `users/${id}`);
  const query = useDatabaseValue(["user", id], dbRef);

  if (query.isLoading) {
    return <div>Loading...</div>;
  }

  if (!query.data) {
    return <div>Please sign-in!</div>;
  }

  return <div>Welcome {query.data.displayName}</div>;
}

To subscribe to value changes on the reference, pass the subscribe option to the hook:

// Subscribe to value changes
const query = useDatabaseValue(["user", id], dbRef, {
  subscribe: true,
});

You can also pass along useQuery hook options, for example to return a specific value from the snapshot and be notified on success:

const query = useDatabaseValue(["user", id], dbRef, undefined, {
  select(data) {
    return data?.displayName;
  },
  onSuccess(displayName) {
    console.log("Fetched the users display name!", displayName);
  },
});

if (query.isSuccess) {
  console.log("Users display name is: ", query.data);
}

TypeScript users can also provide a return type for the data response:

type Profile = {
  displayName: string;
  age: number;
};

const query = useDatabaseValue<Profile, number | null>(
  ["user", id],
  dbRef,
  undefined,
  {
    select(data) {
      return data?.age;
    },
  }
);

if (query.isSuccess) {
  // query data will be typed as number | null
  console.log("Users age is: ", query.data);
}

Working with arrays

By default the useDatabaseValue hook will return the entire contents of the ref as it appears within the database (an object or value). If the database reference provided is an object which is designed to act as a list of ordered data, the hook can also return an array of ordered data instead.

For example, assume you push new events to an audit trail of the user:

const auditRef = ref(db, "users/123/audit");

await set(push(auditRef), "User logged in!");

// Sometime later
await set(push(auditRef), "User logged out!");

We can now return the data as an array with ordered values by providing the toArray option:

const auditRef = ref(db, "users/123/audit");

const query = useDatabaseValue(["user", "123", "audit"], dbRef, {
  toArray: true,
});

if (query.isSuccess) {
  console.log(query.data);
  /**
   * [
   *   "User logged in!",
   *   "User logged out!",
   * ]
   */
}

useDatabaseSetMutation

To set data in the database, use the useDatabaseSetMutation hook. The hook wraps around the useMutation hook.

const dbRef = ref(database, `user/123`);

const mutation = useDatabaseSetMutation(dbRef);

mutation.mutate({
  displayName: "John Doe",
});

To set data with priority, provide the priority option:

const mutation = useDatabaseSetMutation(dbRef, {
  priority: "high",
});

TypeScript users can override the generic unknown type on the setting of data:

const dbRef = ref(database, `user/123`);

type User = {
  displayName: string;
  age: number;
};

const mutation = useDatabaseSetMutation<User>(dbRef);

// Mutate call is now typed as `User`
mutation.mutate({
  displayName: "John Doe",
  age: 18,
});

useDatabaseUpdateMutation

To update data in the database, use the useDatabaseUpdateMutation hook. The hook wraps around the useMutation hook.

const dbRef = ref(database, `user/123`);

// Example data set in the database
await set(dbRef, {
  displayName: "John Doe",
  address: {
    line1: "Sunset Boulevard",
  },
});

const mutation = useDatabaseUpdateMutation(dbRef);

mutation.mutate({
  displayName: "John Doe",
  "address/line1": "Sunrise Boulevard",
});

TypeScript users can override the generic Record<string, unknown> type on the updating of data:

const dbRef = ref(database, `user/123`);

type User = {
  displayName: string;
  "address/line1": string;
};

const mutation = useDatabaseUpdateMutation<User>(dbRef);

// Mutate call is now typed as `User`
mutation.mutate({
  displayName: "John Doe",
  "address/line1": "Sunrise Boulevard",
});

useDatabaseRemoveMutation

To remove data in the database, use the useDatabaseRemoveMutation hook. The hook wraps around the useMutation hook.

const dbRef = ref(db, "user/567");

const mutation = useDatabaseRemoveMutation(dbRef);

// Calling mutate will delete the user.
mutation.mutate();

useDatabaseTransactionMutation

To perform a database transaction, use the useDatabaseTransactionMutation hook. The hook wraps around the useMutation hook.

const dbRef = ref(db, "post/123");

const mutation = useDatabaseTransactionMutation(dbRef, (post) => {
  if (post) {
    post.likes++;
  }

  return post;
});

// Calling mutate trigger the transaction
mutation.mutate();

If you wish to get the result of the transaction (a TransactionResult), either await the mutation or wait for it to be successful:

const dbRef = ref(db, "post/123");

const mutation = useDatabaseTransactionMutation(dbRef, (post) => {
  if (post) {
    post.likes++;
  }

  return post;
});

// Option 1: Await the mutation
const transactionResult = await mutation.mutateAsync();

// Option 2: Wait for success
if (mutation.isSuccess) {
  const transactionResult = mutation.data;
  console.log(transactionResult.committed);
  console.log(transactionResult.snapshot);
}

TypeScript users can provide a typed transaction value rather than the default any type:

const dbRef = ref(db, "post/123");

type Post = {
  title: string;
  likes: number;
}

const mutation = useDatabaseTransactionMutation<Post>(dbRef, (post: Post | null) => {
  if (post) {
    post.likes++;
  }

  return post;
});

// Calling mutate trigger the transaction
mutation.mutate();