Coder Social home page Coder Social logo

invertase / react-query-firebase Goto Github PK

View Code? Open in Web Editor NEW
367.0 10.0 66.0 750 KB

React Query hooks for managing asynchronous operations with Firebase. Supports Authentication, Analytics, Firestore & Realtime Database.

Home Page: https://react-query-firebase.invertase.dev

License: Apache License 2.0

TypeScript 98.31% JavaScript 1.69%
react-query firebase firestore firebase-auth authentication functions database realtime-database analytics react-hooks

react-query-firebase's Introduction

Important

This project is not currently being maintained. If you are interested in maintaining, please reach out on Discord to a member of the team.

React Query Firebase

A set of React Query hooks integrating with Firebase.

Installation DocumentationLicense


React Query Firebase provides a set of easy to use hooks for handling asynchronous tasks with Firebase in your React application.

Why should I use React Query Firebase?

  • Backed by React Query - Unlike other solutions, hooks are built on top of React Query which takes care of complex challenges such as caching, automatic refetching, realtime data subscriptions, pagination & infinite queries, mutations, SSR Support, data selectors, side effect handlers and more. You also get DevTool support out of the box!
  • Un-opinionated - You provide the Query Keys, Configuration & Firebase instances, allowing for full control over how your data is integrated and cached. You can also roll it alongside any existing Firebase usage.
  • Performant & Efficient - Whether your queries are one-off or realtime, the library is designed to be performant and efficient. Data fetching is handled via Queries and Query Keys, meaning components can share data throughout your application without needless database reads.
  • Mutations - Sign a user in, delete a document, run a transaction, log an event... React Query Firebase takes care of that for you via Mutations, allowing you to focus on your application and not managing complex local loading & error states.
  • Fully Typed - The library is built with and has full compatibility with TypeScript.

Note: The library supports the Firebase JS SDK v9 - learn more about it here!

Example

As an example, let's use a Firestore hooks to fetch a document & run a transaction whilst easily handling asynchronous state.

import {
  useFirestoreDocument,
  useFirestoreTransaction,
} from "@react-query-firebase/firestore";
import { doc } from "firebase/firestore";
import { firestore } from "./config/firebase";

type Product = {
  name: string;
  price: number;
};

function ProductPage({ id }: { id: string }) {
  // Create a Firestore document reference
  const ref = doc(firestore, "products", id);

  // Query a Firestore document using useQuery
  const product = useFirestoreDocument<Product>(
    ["product", id],
    ref,
    {
      // Subscribe to realtime changes
      subscribe: true,
      // Include metadata changes in the updates
      includeMetadataChanges: true,
    },
    {
      // Optionally handle side effects with React Query hook options
      onSuccess(snapshot) {
        console.log("Successfully fetched product ID: ", snapshot.id);
      },
    }
  );

  // Run a Firestore transaction as Mutation using useMutation
  const like = useFirestoreTransaction(
    auth,
    async (tsx) => {
      const record = await tsx.get(ref);
      tsx.update(ref, {
        likes: record.data().likes + 1,
      });
    },
    {
      onError(error) {
        console.error("Failed to like product!", error);
      },
    }
  );

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

  if (product.isError) {
    return <div>Failed to fetch product: {product.error.message}</div>;
  }

  const snapshot = product.data; // DocumentSnapshot<Product>

  return (
    <div>
      <h1>{snapshot.data().name}</h1>
      <button disabled={like.isLoading} onClick={() => like.mutate()}>
        Like Product!
      </button>
      {like.isError && <p>Failed to like product: {like.error.message}</p>}
    </div>
  );
}

Installation

If you haven't done so already, install react, react-query & firebase (v9):

npm i --save react react-query firebase

Before using this library, ensure React Query is setup on your project by following the Installation guide.

Next install one of the React Query Firebase packages, e.g:

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

See below for a full list of available packages.

Packages

License


Built and maintained by Invertase.

react-query-firebase's People

Contributors

amonlibanio avatar bvangraafeiland avatar cabljac avatar dackers86 avatar ehesp avatar eric013 avatar jbethuel avatar salakar avatar vomchik avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-query-firebase's Issues

How do I pass variables to hooks?

When I've used React Query before, I used it as a hook. Like Tanner showed in this demo: https://youtu.be/DocXo3gqGdI?t=2817

And also in the React Query Docs, https://tanstack.com/query/v4/docs/guides/mutations
It shows how data can be passed into a useMutation that runs inside mutationFn. This allows you to abstract a 'useUpdateMyData' hook, or something like that. And it has all of the fetching logic inside, and you just pass a small amount of data to make it mutate different documents.

function App() {
  const mutation = useMutation({
    mutationFn: newTodo => { // Right here, data comes in as newTodo
      return axios.post('/todos', newTodo)
    }
  })

  return (
    <div>
      {mutation.isLoading ? (
        'Adding todo...'
      ) : (
        <>
          {mutation.isError ? (
            <div>An error occurred: {mutation.error.message}</div>
          ) : null}

          {mutation.isSuccess ? <div>Todo added!</div> : null}

          <button
            onClick={() => {
              mutation.mutate({ id: new Date(), title: 'Do Laundry' })
            }}
          >
            Create Todo
          </button>
        </>
      )}
    </div>
  )
}

I incorporated that into custom hooks on other projects:

import 'react-native-get-random-values';
import Constants from 'expo-constants';
import { useMutation, useQueryClient } from 'react-query';
import getDbName from '../../utils/loadDatabaseName';
const CosmosClient = require('@azure/cosmos').CosmosClient;

const containerId = 'logbooks';

export const useDeleteLogbook = () => {
  const queryClient = useQueryClient();
  const { mutate: deleteLogbook } = useMutation(
    newLogbook => mutation(newLogbook),
    {
      onSuccess: () => {
        queryClient.invalidateQueries('logbooks');
      },
    }
  );
  return { deleteLogbook };
};

const mutation = async logbook => { // Right here, my dynamic data comes into my hook as 'logbook'
  const endpoint = await Constants.manifest.extra.cosmosEndpoint;
  const key = await Constants.manifest.extra.cosmosKey;
  const databaseId = await getDbName();
  const client = new CosmosClient({ endpoint, key });

  await client
    .database(databaseId)
    .container(containerId)
    .item(logbook.id, logbook.userId)
    .delete();
};

Then in my code, I could use the hook and it was incredibly clean:


// Inside react component:
const { deleteLogbook } = useDeleteLogbook();
          onPress: () => deleteLogbook(logbook),

But now I'm trying to do the same thing with this package, and I'm running into trouble.

I've implemented a custom deletion hook like so. But I can't find a way to pass dynamic variables in as before.

import { collection, doc } from 'firebase/firestore';
import { useFirestoreDocumentDeletion } from '@react-query-firebase/firestore';
import { db } from '../../utils/firebase';

export const useDeleteLogbook = () => {
  const collection = collection(db, 'logbooks');
  const ref = doc(collection, '456');// I want to pass a dynamic value here instead of 456
  const { mutation: deleteLogbook } = useFirestoreDocumentDeletion(ref);
  return { deleteLogbook };
};

According to your example, you call the mutate function with no data passed in. Just mutation.mutate()

 <button
        disabled={mutation.isLoading}
        onClick={() => {
          mutation.mutate(); // No data passed in
        }}
      >
        Delete document
      </button>

https://react-query-firebase.invertase.dev/firestore/data-mutation

So I'm wondering, how do we implement this with dynamic data? For example, if I have a list, where each item has a delete button. How do I pass in mutation.mutate(<clickedElement'sId>) so that my react-query-firebase code can delete the correct document?

Could you point me in the right direction or give me a small example?

I tried reading the code of this package, but I'm not great with typescript yet.

Using invalidateQueries function makes useFirestoreQueryData to return old data

I'm trying to optimistically update a document in firestore, but on calling the invalidateQueries function once useMutation is settled I'm getting the old document data, though the document has been updated in the firebase. Also initially I get the updated data but after that, it gets replaced with the old data.

 onMutate: async ({ currData }) => {
      // Cancel any outgoing refetches (so they don't overwrite our optimistic update)
      await queryClient.cancelQueries(["auth-user"]);

      // Snapshot the previous value
      const previousUserData = queryClient.getQueryData(["auth-user"]);

      // Optimistically update to the new value
      queryClient.setQueryData(["auth-user"], () => currData);

      // Return a context object with the snapshotted value
      return { previousUserData };
    },
    // If the mutation fails, use the context returned from onMutate to roll back
    onError: (_error, _passedUser, context) => {
      console.log("error", _error);
      toast.show({
        title: "Couldn't update user profile!",
      });
      queryClient.setQueryData(["auth-user"], context?.previousUserData);
    },
    // Always refetch after error or success:
    onSettled: () => {
      queryClient.invalidateQueries(["auth-user"]);
    }
const {
    data: user,
    isLoading: isLoadingUser,
    isFetching,
    error: userError,
  } = useFirestoreDocumentData(
    ["auth-user"],
    doc(db, "users", userId),
    {
      subscribe: true,
    }
  );

FirebaseError: Type does not match the expected instance... different Firestore SDK?

I'm working on an app in Nextjs 12 and everything appears to be working fine but I keep getting this error at build time about a different Firestore SDK:

error - unhandledRejection: [FirebaseError: Type does not match the expected instance. Did you pass a reference from a different Firestore SDK?] {
  code: 'invalid-argument',
  customData: undefined,
  toString: [Function (anonymous)]
}

Everythig compiles and the site works fine aside from this error but I'd like to understand the problem if possible. Sorry I don't have much more to share, the error doesn't indicate much.

Here's more on my env:

    "@react-query-firebase/firestore": "^1.0.0-dev.7",
    "firebase": "^9.9.3",
    "next": "12.2.5",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-query": "3.*"

Edit: Should add that I am working in JS not TS.

useAuthSignInWithEmailAndPassword returns misleading errors

Hi, I'm building a login with firebase and react-query-firebase and I have some weird problems with the errors which the hook returns There's a lot of problems that can make fail a login attempt... but useAuthSignInWithEmailAndPassword returns auth/network-request-failed to everyone of them.

My test code:

import React from 'react'
import { auth } from '../utils/firebase'
import { Link, useNavigate } from 'react-router-dom'
import { useAuthSignInWithEmailAndPassword } from '@react-query-firebase/auth'
import { Container } from '../components'

const Login = () => {

    const navigate = useNavigate()
    const [form] = Form.useForm()
    const signInMutation = useAuthSignInWithEmailAndPassword(auth, {
        onSuccess: () => navigate("/admin"),
        onError: err => {
            console.log(err)
        }
    })

    const handleLogin = async () => {
        const { email, password } = form.getFieldsValue(true)
        signInMutation.mutate({ email, password }) 
    }

    return (
        <Container>
            <Form 
                form={form} 
                layout="vertical"
                onFinish={handleLogin}
            >
                <Form.Item
                    label="Email"
                    name="email"
                >
                    <Input />
                </Form.Item>
                <Form.Item
                    label="Password"
                    name="password"
                >
                    <Input.Password />
                </Form.Item>
                <Button type="primary" onClick={() => form.submit()}>Login</Button>
            </Form>
            <br />
            <Link to="/">Back to home</Link>
        </Container>
    )
}

export default Login

If I inspect the Network tab of the browser I can see that the server response shows the real error code, in this case MISSING_EMAIL, but it has the same behaviour with MISSING_PASSWORD, EMAIL_NOT_FOUND, INVALID_EMAIL or INVALID_PASSWORD. All of them just return auth/network-request-failed as error.
image

Firebase is well configurated and working and obviously I have internet connection. In fact, if the user/pass is correct it just works fine.

auth/network-request-failed should only arise when there's connection or maybe Firebase config problems, but not problems with the user/password pair, this makes difficult to debug problems with authentication and impossible to return to the user a more semantic error when entering a bad login pair.

prefetching data ?

Would it be possible to pre fetch data just like react-query offers ?
Just an idea...

[Question] Dependant queries and paths

I was wondering how you are dealing with a segment of the path being undefined on the first render when one refreshes the page or opens a copy pasted deep link in a new tab

I just did it like this, but im sure there must be a better pattern?

const useGetItem = ({ id = '' }) => {
  const { user } = useAuth();

  // Necesary evil for now because the query value of nextjs router is initially empty and rules of hooks discourage conditional calling.
  
  const path = user?.uid ? `users/${user?.uid}/items/${id}` : 'users/1';

  const itemDocReference = doc(firestore, path).withConverter(ItemConverter);

  return useFirestoreDocumentData(
    ['item', id],
    itemDocReference,
    { idField: 'id' },
    { enabled: !!user?.uid }
  );
};
const useGetUser = () => {
  const { user } = useAuth();
  const userDocReference = doc(firestore, `users/${user?.uid}`).withConverter(
    UserConverter
  );

  return useFirestoreDocumentData(
    ['users'],
    userDocReference,
    {
      idField: 'uid'
    },
    {
      enabled: !!user?.uid
    }
  );
};

Error: No QueryClient set, use QueryClientProvider to set one

Problem:
react-query-firebase always show the error Error: No QueryClient set, use QueryClientProvider to set one. My provider is given like in the newest docs.

What I've tried

  • Setting -> <ReactQueryDevtools initialIsOpen={false} />
  • Setting -> contextSharing={true}>

__app.tsx

<QueryClientProvider client={queryClient} contextSharing={true}>
    <Navbar />
        <Component {...pageProps} />
        {/* <ReactQueryDevtools initialIsOpen={false} /> */}
</QueryClientProvider>

useFirestoreDocument usage error

Error: No QueryClient set, use QueryClientProvider to set one
  const docRef = doc(collection(db, "users"), uid);
> 35 |   const extendedUser = useFirestoreDocument(["users", uid], docRef);

System and npm info:
OS: Mac OS Monterey
"firebase": "^9.9.2",
"@tanstack/react-query": "^4.1.0",
"next": "12.2.4",
"react": "18.2.0",

How to query subset of data

Apologies if this is outside the scope of this, but I'm attempting to port an application across from AngularJS to React and hoping there may be a solution here.

So my RTDB structure is set up as follows:

-customers
    -customer1
        -orders
            -key1:true
            -key2:true
            -key3:true
            
-orders
    -key1
    -key2
    -key3

Previously with AngularFire (for AngularJs) I could use the $extend functionality allowing me to return an array of Firebase objects with:

  var customerOrders = firebase.database().ref('customers/' + customerId + '/orders/');
  var orders = firebase.database().ref('orders');
  var OrdersList = $firebaseArray.$extend({
    $$added: function (snap) {
      return new Promise(function (resolve, reject) {
        $firebaseObject(orders.child(snap.key)).$loaded().then(function (orderData) {
          resolve(orderData);
        }).catch(reject);
      });
    }
  });
  return new OrdersList(customerOrders);

Optimistic updates

How to implement optimistic updates using useFirestoreDocumentMutation hook?

How to write tests on these hooks?

Hi, I have been researching for about a week how to test react query hooks and I couldn't find anything.

I am new to testing and very confused, I have few questions.

  1. I can see that you have tested these hooks, so do I need to do it myself?
  2. If I am mocking the api, what exactly am I testing, since the data is not going to be from real database?
  3. is it common not to write automated for these hooks, since it just works?

Here is a sample code below, how would I write a simple, purposeful working test with jest in this case?

function GetUser() {
  const id = "pW5CizOJOpXezr5lGGshDmKdVzZ2";
  const ref = doc(firestore, "users", id);
  const user = useFirestoreDocument(["users", id], ref);

  const name = user.data?.data()?.name;

  return (
    <div>
      {user.isLoading && <div>Loading...</div>}
      {name && <p>{name}</p>}
    </div>
  );
}

export default GetUser;

I am sorry, but I am very confused, so I would appreciate any help so much.

Error in useAuthCreateUserWithEmailAndPassword function

I found that the useAuthCreateUserWithEmailAndPassword function has a targeting error.

function useAuthCreateUserWithEmailAndPassword(auth, useMutationOptions) {
return reactQuery.useMutation(function (_a) {
var email = _a.email, password = _a.password;
return auth$1.confirmPasswordReset(auth, email, password); Error
}, useMutationOptions);
}

function useAuthCreateUserWithEmailAndPassword(auth, useMutationOptions) {
return reactQuery.useMutation(function (_a) {
var email = _a.email, password = _a.password;
return auth$1.createUserWithEmailAndPassword(auth, email, password); Correct
}, useMutationOptions);
}

pass useInfiniteQueryOptions to useInfiniteQuery

I believe that the useInfiniteQueryOptions are not being passed down from useFirestoreInfiniteQuery and useFirestoreInfiniteQueryData to the underlying useInfiniteQuery call?

The other query methods like useFirestoreQuery do seem to pass down the options like useQueryOptions.

I made a pull request to be able to set getPreviousPageParam #40 but honestly I don't know TypeScript well enough to get it to pass the rest of the options correctly so was hoping to get some help there.

How to query array of references

Hi, first I want to say thank you for making this awesome symbiosis of react-query and Firebase.

I was wondering how to approach to query an array of references.
For example, my data looks like this:

{
  email: "[email protected]"
  locations: [Ec2, Ec2, Ec2]
  roles: (3) ['master', 'manager', 'scout']
  username: "Winona14"
}

Here, "locations" is an array of Firebase references.
What would be the best approach to fetch those references? Since the hooks don't support array of docs.
Something like this would be great, under the hood it could use something like useQueries from 'react-query'

  const { data: locations, loading, error } = useFirestoreDocumentData<any>(
    ['locations', auth.uid],
    user.locations.map(location => doc(FireBase.db, location.path)),
    {},
    { enabled: Boolean(auth.uid) },
  );

if there is no way to do this currently, I will try to make a PR to support to fetch arrays of docs

Thank you

Modify subscription tests not to rely on "onSuccess"

In our subscription hooks, specifically in the internal hook useSubscription, we use the setQueryCache method to update data.

In our tests we are waiting for onSuccess to be called, which no longer happens.

The suggestion in the tanstack docs is to have a useEffect hook depending on the data. I'm not sure how this will look in tests. Will have to investigate.

Uncaught ReferenceError: Cannot access 'query' before initialization

I just tried the example at: https://react-query-firebase.invertase.dev/firestore/querying-collections
and got this error above.

If I move the query before the function it will work, but then I am not able to pass parameters to the querys where clause.

I am using React 16.14.0.

import React from "react";
import { useFirestoreQuery } from "@react-query-firebase/firestore";
import {
  query,
  collection,
  limit,
  QuerySnapshot,
  DocumentData,
} from "firebase/firestore";
import { firestore } from "../firebase";

function Products() {
  // Define a query reference using the Firebase SDK
  const ref = query(collection(firestore, "products"));

  // Provide the query to the hook
  const query = useFirestoreQuery(["products"], ref);

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

  const snapshot = query.data;

  return snapshot.docs.map((docSnapshot) => {
    const data = docSnapshot.data();

    return <div key={docSnapshot.id}>{data.name}</div>;
  });
}

Compatibility with firebase/compat/app

I have stumbled with CORS 'Access-Control-Allow-Origin issues when trying to start a session with the node.js v9 firebase library in Firefox.

However, when using the v9 Firebase compat APIs, they are not supported by react-query-firebase (due to Typescript warnings), so how should I handle this? Thanks.

Cannot get previousPage when using useFirestoreInfiniteQuery or useFirestoreInfiniteQueryData

Looks like the code inside getPreviousPageParam is not even being executed.

Example:

 const ordersQuery = query(
    collection(firestore, "orders") as CollectionReference<Order>,
    orderBy("createdAt"),
    limit(1)
  );
  const {
    data,
    hasPreviousPage,
    hasNextPage,
    fetchPreviousPage,
    fetchNextPage,
  } = useFirestoreInfiniteQueryData(
    ["getOrders"],
    ordersQuery,
    (snapshot) => {
      const lastDocument = snapshot[snapshot.length - 1];
      if (!lastDocument) return;
      return query(ordersQuery, startAfter(lastDocument.createdAt));
    },
    {},
    {
      getPreviousPageParam: (firstPage) => {
        const firstOrder = firstPage[0];
        return query(ordersQuery, endBefore(firstOrder.createdAt));
      },
    }
  );

The result is the same when loading the component and after click a few times in next page button.
console.log(hasPreviousPage, hasNextPage);

image

Also tried to console.log inside getPreviousPageParam and nothing happens.

I'm I doing something wrong?

How to write tests for this hooks using Jest/RTL?

How can I write tests for this, I've tried mocking the hooks but nothing works, I'd like to start with a simple test flow:

-> Type on e-mail input ✅
-> Type on password input ✅
-> Click the Sign In button ✅
-> Wait for hook return isLoading:true 🚫

Another test example that I've tried
-> Type on e-mail input ✅
-> Type on password input ✅
-> Click the Sign In button ✅
-> Wait for hook return authenticated user object 🚫

Thank you! ❤️

I have the following code:

import { Button } from '@suwilo/ui';
import { AuthError, EmailAuthProvider } from 'firebase/auth';
import { useSignInWithEmail } from '../../hooks/useSignInWithEmail';
import { Form, Formik, FormikHelpers } from 'formik';
import * as Yup from 'yup';
import { useTranslation } from 'react-i18next';
import { Input } from '../Input/Input';

type SignInFormProps = { email: string; password: string };

type CodeProps = {
  [name: string]: [string, string];
};

export const SignInForm = () => {
  const { mutate: login } = useSignInWithEmail();
  const { t } = useTranslation();

  const initialValues = {
    email: '',
    password: '',
  };

  const schema = Yup.object().shape({
    email: Yup.string()
      .email(t('@SignInForm.email-invalid'))
      .trim()
      .required(t('@SignInForm.email-required')),
    password: Yup.string().required(t('@SignInForm.password-required')).trim(),
  });

  const onSubmit = (
    values: SignInFormProps,
    actions: FormikHelpers<SignInFormProps>
  ) => {
    const credentials = EmailAuthProvider.credential(
      values.email,
      values.password
    );

    const onError = (error: AuthError) => {
      const codes: CodeProps = {
        'auth/user-not-found': ['email', t('firebase.auth/user-not-found')],
        'auth/wrong-password': ['password', t('firebase.auth/password-wrong')],
      };

      const args = codes[error.code];

      actions.setFieldError(...args);
    };

    login(credentials, {
      onError,
    });
  };

  return (
    <Formik
      initialValues={initialValues}
      validationSchema={schema}
      onSubmit={onSubmit}
    >
      {() => (
        <Form className="mt-8 grid grid-cols-6 gap-6">
          <div className="col-span-6">
            <Input
              type="email"
              id="Email"
              name="email"
              required
              label={t('@SignInForm.email')}
              placeholder={t('@SignInForm.email')}
            />
          </div>

          <div className="col-span-6">
            <Input
              type="password"
              id="Password"
              required
              name="password"
              label={t('@SignInForm.password')}
              placeholder={t('@SignInForm.password')}
            />
          </div>

          <div className="col-span-6">
            <a className="text-sm text-primary-500 mx-1 hover:cursor-pointer hover:text-primary-700">
              {t('@SignInForm.forgot-password')}
            </a>
          </div>
          <div className="col-span-6">
            <Button className="w-full" type="submit">
              {t('@SignInForm.sign-in')}
            </Button>
          </div>

          <div className="col-span-6">
            <hr className="border-gray-100" />
          </div>
        </Form>
      )}
    </Formik>
  );
};

[new feat sugg] Firestore / Dynamic Parallel Queries

As react query implements dynamic parallel queries with useQueries, this library would similarly benefit from running multiple queries in parallel.

Use case:
Given the following structure /parents/{parentId}/children/{childrenId}
I want to get the first n children for parents X,Y, and possibly Z (without knowing beforehand how many parents will be queried)

considerations

  • we don't want to use groupCollection
  • in this case it's the same query, repeated multiple times, but we could have different queries

Getting insufficient permission status

I created a hook

import { doc } from "@firebase/firestore";
import { useFirestoreDocumentData } from "@react-query-firebase/firestore";
import { UseQueryResult } from "react-query";
import { db } from "../utils/firebase";

export interface User {
  displayName: string;
}
export const GET_USER = "GET_USER";

const useGetUser = (
  id: string,
  enabled: boolean
): UseQueryResult<User, unknown> => {
  const ref = doc(db, "users", id);
  return useFirestoreDocumentData([GET_USER, id], ref, undefined, { enabled });
};

export default useGetUser;

When I use this hook and I have insufficient permission because a security rule, I could not read this status out of the hook return values. The status is loading and the data is undefined. How can I get the security exception?

useFirestoreInfiniteQuery fetchNextPage() causes error.

The error:

Unhandled Runtime Error
FirebaseError: Function startAfter() called with invalid data. Unsupported field value: undefined

I get this error, when I click on the Load More button twice, my collection has no more documents, but seems like the button is not disabled and lastDocument is undefined which gets passed to startAfter().

const posts = useFirestoreInfiniteQuery("posts", postsQuery, (snapshot) => {
  const lastDocument = snapshot.docs[snapshot.docs.length - 1];

  // Get the next 20 documents starting after the last document fetched.
  return query(postsQuery, startAfter(lastDocument));
});

fetchNextPage() causes this error.

const posts = useFirestoreInfiniteQuery(...);

<button
  disabled={posts.isLoading}
  onClick={() => posts.fetchNextPage()}
>
  Load More
</button>

What is the reason for this bug?

Using { subscribe: true } with useFirestoreQueryData causes infinite loading status.

Using { subscribe: true } with useFirestoreQueryData or useFirestoreQuery causes infinite loading status when I refresh a page in Nextjs app.

const ContactsLandingPage: React.FC = (): React.ReactElement => {
  // ? next router
  const { push } = useRouter()

  // ? permissions gate
  const {
    customClaims: { accountId }
  } = usePermissionsGateContext()

  // ? the firebase services
  const firestore = useFirestore()

  // ? the contacts
  const contactsColRef = collection(firestore, `customer_accounts_data/${accountId}/contact`).withConverter(
    contactConverter
  )
  const {
    status: contactsStatus,
    error: contactsError,
    data: contacts
  } = useFirestoreQueryData(['contacts', { accountId }], contactsColRef, { subscribe: true }, {})
"firebase": "^9.4.0",
"@react-query-firebase/firestore": "^0.4.2",
"react-query": "^3.32.3",

image

Querying firestore documents with subscribe never fetches

just never finishes fetching. setting subscribe to false fetches instantly

const ref = doc(firestore, "games", id);

const { data, isLoading } = useFirestoreDocumentData(["games", id], ref, {
    subscribe: true,
});

image

it does work with useFirestoreQuery

"@react-query-firebase/firestore": "^1.0.0-dev.7"
"firebase": "^9.11.0"
"react-query": "^3.39.2"

Pass doc ref in `useFirestoreDocumentMutation` callback (not the hook)?

In a todo list app, one would use useFirestoreDocumentMutation to mark the individual items as TODOs. The current API takes a doc reference in the hook. If there are 100 todos, each one needs it's own hook of useFirestoreDocumentMutation so that would be 100 calls to useFirestoreDocumentMutation. Since each of these calls react-query's useMutation, which in turn calls 6 hooks. This can be very costly, even though each one is doing the same thing (save for on a different doc).

Ideally, we would only need one call to useFirestoreDocumentMutation once, like this

  const TodoList = ({ todos }) => {
    const mutation = useFirestoreDocumentMutation()
    return (
      <div>
        {todos.map((todo) => (
          <Todo
            {...todo}
            key={todo.id}
            onComplete={() =>
              mutation([doc(db, 'todo', todo.id), { ...todo, completed: true }])
            }
          />
        ))}
      </div>
    )
  }

See docs for useMutation.

react-native-firebase support

Love the idea of this library. I know that it's depending on firebase js v9, is there a way we can shim in support for react-native-firebase?

Discussion: production readiness

Hi, first I would like to thank you for this amazing library, I am planning to use it for a production app that will be launching soon. I am a bit worried if the lib is production ready. Anyone willing to share their experience using it in prod? My concerns are mainly related to any unexpected Firebase charges, especially in the current state of lib, where to make the real time query work we needed to set the cacheTime: 0

new QueryClient({
        defaultOptions: {
          queries: {
            retry: 3,
            retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
            staleTime: 10 * 60 * 1000, // 10 min
            cacheTime: 0, // 5 min (default)
            refetchOnWindowFocus: false
          }
        }
      })

Also it seems that there's some stale issues, is the lib looking for maintainers?

1.0.0-dev7: useFirestoreDocumentData does not update invalidated data (react 18.2.0, react-query 3.39.2)

After bumping @react-query-firebase/firestore (mostly to workaround React 18.2.0 peer dep), the query invalidation stops working.
I do not have time to create minimal repro sandbox. Posting as a heads-up to others.
Reverting to 0.4.2 fixes the issue.

Simplified pseudo-code:

// Disable auto-refetching to avoid noise.
const queryClient = new QueryClient({defaultOptions: {queries: {staleTime: Infinity, cacheTime: Infinity}}});
<QueryClientProvider client={queryClient}>...</...>

const ref = docRef.withConverter(converter);
// simplified, just to demonstrate invalidation method.
const onSuccess = async () => ({await queryClient.invalidateQueries({predicate: () => true})});
const {data} = useFirestoreDocumentData(path, ref),  // not a subscription
      {mutate} = useFirestoreDocumentMutation(docRef, {merge: true}, {onSuccess});

Actual results when calling mutate({field: someVal})

  • The value is updated is Firebase
  • onSuccess is called
  • The query is invalidated as expected and refetched, according to network/logs/react-query-devtools
  • According to debug logs, after refetching the converter applied on ref is called with proper "new" value

Until this point, works as expected. But the values are not updated in:

  • query data in react-query-devtools,
  • useFirestoreDocumentData hook data in React Dev Tools,
  • the app.

react-query-firebase hooks do not work in a Next.js app until AFTER a vanilla Firebase API is made.

Hi,
Great package so far but I'm having an issue where I get the following error: "FirebaseError: Expected first argument to collection() to be a CollectionReference, a DocumentReference or FirebaseFirestore".

I am building a Next.js app (but no SSR or SSG). I am currently refactoring our Firebase API (vanilla JS) with react-query-firebase hooks.

When a page is loaded (or refreshed), I see the above error message. At first, I thought it was something specific with Next.js (and it could very well be). I then realized that using a react-query-firebase hook such as 'useFirestoreQueryData' anywhere in the app provides the above error message right away. However, if I use one of our vanilla Firebase API calls BEFORE using a react-query-firebase hook, everything works just fine.

I believe that react-query-firebase is NOT reading the Firebase initializeApp code (which is set up according to both this library's documentation and the official Firebase documentation). To confirm this, I have made a simple "dummy" Firebase API call in our app.js file (the file where we set up queryClient). Doing this "hack" fixes the above issue right away.

Please advise on what to do. Perhaps it's not a bug and i'm simply missing something from your documentation. Also, still could be a Next.js specific issue, however, we have built other Next.js apps with react-query (non-firebase, graphQL) and that worked perfectly fine too.

Thanks!

Dependent Queries on uid using useFirestoreDocument hook

I am trying to pass user uid from useAuthUser hook to my useFirestoreDocument.

However,

const user = useAuthUser(["user"], auth);
const ref = doc(firestore, "users", user.data.uid);

Does not work because user is originally initialized as undefined, before it is populated and you get error.

In React-Query there is an optional field enabled that can pause the query until it's ready to be executed https://react-query.tanstack.com/guides/dependent-queries

Can this be done with react-query-firebase hooks as well?

Not able to fetchNextPage using useFirestoreInfiniteQueryData

Hello, i have been trying to use the useFirestoreInfiniteQueryData hook, but when i try to fetch the next page i just get an empty array, which should contain 1 record more (3 in total), if anybody can recommend better practices feel free to leave a comment

This is the code to fetch the next page

type GetPatientsQueryContext = {
  pageIndex: number
  limit: number
}
export const useGetPatientsQuery = (
  workspaceId: string | undefined,
  context: GetPatientsQueryContext,
) => {
  const hasParameters = Boolean(workspaceId)
  const queries = hasParameters
    ? [
        limit(context.limit),
        where('workspaceId', '==', workspaceId),
        orderBy('createdAt'),
      ]
    : []
  const patientCollection = getCollection('patients')
  const q = query(patientCollection, ...queries)

  return useFirestoreInfiniteQueryData(
    ['patients', { workspaceId, ...context }],
    q,
    (documentData) => {
      if (!Boolean(documentData.length)) return undefined // Added this because when clicking fetchNextPage documentData has length of 0, which gives an error on startAfter
      const lastDocument = documentData[documentData.length - 1] // On first load if i add a console log here we correctly see the last doc which is "Zenaida Odom"
      return query(q, startAfter(lastDocument))
    },
    {},
    {
      getPreviousPageParam: (documentData) => {
        const firstDocument = documentData[0]
        return query(q, endBefore(firstDocument), limitToLast(context.limit))
      },
      select: (data) => {
        const castedPages = data.pages as Array<PatientRawData[]>
        const parsedPages = castedPages.map((page) => {
          return page.map((patient) => ({
            ...patient,
            createdAt: patient.createdAt.toDate(),
            updatedAt: patient.updatedAt?.toDate(),
          }))
        })
        return {
          pages: parsedPages as Array<PatientData[]>,
          pageParams: data.pageParams,
        }
      },
      enabled: hasParameters,
      refetchOnWindowFocus: false,
    },
  )
}

And i call this custom hook like this

  const { workspaceState } = useWorkspaceContext()
  const [pageIndex, setPageIndex] = React.useState(0)

  const patientsQuery = useGetPatientsQuery(workspaceState.workspace?.id, {
    pageIndex,
    limit: 2,
  })

  console.log(patientsQuery.data)

  const nextPage = async () => {
    if (!patientsQuery.hasNextPage) return
    await patientsQuery.fetchNextPage()
    setPageIndex(pageIndex + 1)
  }
  const previousPage = async () => {
    if (!patientsQuery.hasPreviousPage) return
    await patientsQuery.fetchPreviousPage()
    setPageIndex(pageIndex - 1)
  }

  return (
    <div className="patients">
      <button>prev</button>
      <button onClick={nextPage}>next</button>
    </div>
  )

Query data on first load

[ //Pages array
  [ // First page 2 records
    {
      "createdById": "y7SHgg4nTXf9rqqqr6aZORmdoid2",
      "gender": "female",
      "lastName": "Juarez",
      "phone": "+1 (758) 384-6732",
      "id": "vQGcZCnysgBXBCMhH9tB",
      "createdAt": {
        "seconds": 1640622826,
        "nanoseconds": 372000000
      },
      "name": "Gloria Macdonald",
      "workspaceId": "6WoMC9N64EyLhmmZUks1",
      "email": "[email protected]"
    },
    {
      "name": "Zenaida Odom",
      "workspaceId": "6WoMC9N64EyLhmmZUks1",
      "lastName": "Reeves",
      "email": "[email protected]",
      "gender": "female",
      "createdAt": {
        "seconds": 1640668021,
        "nanoseconds": 980000000
      },
      "createdById": "y7SHgg4nTXf9rqqqr6aZORmdoid2",
      "phone": "+1 (423) 561-1783",
      "id": "OkmZv4p0aJDhijGmYzRL"
    }
  ]
]

After clicking nextPage

[ // Pages array
  [ // First page 2 records
    {
      "createdById": "y7SHgg4nTXf9rqqqr6aZORmdoid2",
      "gender": "female",
      "lastName": "Juarez",
      "phone": "+1 (758) 384-6732",
      "id": "vQGcZCnysgBXBCMhH9tB",
      "createdAt": {
        "seconds": 1640622826,
        "nanoseconds": 372000000
      },
      "name": "Gloria Macdonald",
      "workspaceId": "6WoMC9N64EyLhmmZUks1",
      "email": "[email protected]"
    },
    {
      "name": "Zenaida Odom",
      "workspaceId": "6WoMC9N64EyLhmmZUks1",
      "lastName": "Reeves",
      "email": "[email protected]",
      "gender": "female",
      "createdAt": {
        "seconds": 1640668021,
        "nanoseconds": 980000000
      },
      "createdById": "y7SHgg4nTXf9rqqqr6aZORmdoid2",
      "phone": "+1 (423) 561-1783",
      "id": "OkmZv4p0aJDhijGmYzRL"
    }
  ],
  [] // Second page should contain 1 record but its empty instead
]

Query with subscribe enabled makes request after signing out

I have a query that lists all documents in a collection that I subscribe on and if I sign out I keep getting a snapshot listener error because I have a rule that only allows the documents to be accessed if signed in. The component where the query is located gets unmounted properly and doesn't re-render after signing out. I have also tried to remove the query using the query client before signing out but that doesn't seem to remove the snapshot listener but it does stop the realtime changes from working.

toArray - Operates on all child nodes

When using the toArray option from the useDatabaseValue hook, the data returned from the hook is converted to an array at every child node. Is this the expected behavior?

When the root node points to a collection of simple key-value pairs, the toArray method works as expected. However, when introducing JSON it will convert every child node to an array as well.

Working Example

Initial Values

{a:"a", b:"b"},

Result using toArray

Array [ "a", "ab", ],

However, when a is an object rather than a string, the child node is also sorted.

{a:"a", b:{c:"C"},

Array [ "a", Array [ "C", ], ],

Why is it sorting the child node and not just returning the value ?

Can't install with React 18

Hi, seems like it's not possible to install any of the packages on a project using React 18+

$ npm update
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/react
npm ERR!   react@"^18.0.0" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.8.0 || ^17.0.0" from @react-query-firebase/[email protected]
npm ERR! node_modules/@react-query-firebase/auth
npm ERR!   @react-query-firebase/auth@"^0.3.4" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR!

Module not found: Can't resolve '@firebase/auth'

@Ehesp I get this issue when i try to use @react-query-firebase/auth with firebase v9.4.1, the issue surfaces once i try to import
import { useAuthSignInWithEmailAndPassword } from "@react-query-firebase/auth";

and try to use useAuthSignInWithEmailAndPassword hook

useFirestoreDocument/useFirestoreQuery with { subscribe: true } are not subscribing to realtime updates on remount

I've discovered a weird behaviour of useFirestoreDocument/useFirestoreQuery hooks: { subscribe: true } option will have no effect, if query is remounted (in default cacheTime 5 mins window), after became inactive. Probably, It will be good, if query will resubscribe to realtime changes, if there is a snapshot in a cache and there are no active subscribers.

As a workaround we are setting cacheTime to 0, so query will trigger queryFn again and put active subscription on firestore query.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.