TanStack Query Firebase

repository·main·Indexed 19 days ago

https://github.com/invertase/tanstack-query-firebase

Provides integrations for Firebase products with TanStack Query's state management and caching. Includes @tanstack-query-firebase/angular for Angular bindings to Firebase Data Connect, as well as React examples for managing Firebase Authentication and ID tokens using hooks like useGetIdTokenQuery.

Tokens
37.4K
Snippets
130
Records
153
Agent score
63%

What's inside @tanstack-query-firebase

  1. What is TanStack Query Firebase

    main

    TanStack Query Firebase is a library that provides a set of hooks for managing asynchronous Firebase API calls using TanStack Query. It automates state synchronization, reducing the need to manually handle loading states, error states, and data synchronization.

    Key features include:

    • Automatic Caching: Reduces redundant Firebase calls.
    • Out-of-the-box Synchronization: Keeps the UI in sync with the Firebase backend.
    • Background Updates: Seamlessly fetches and syncs data in the background.
    • Error Handling & Retries: Provides automatic retries and robust error handling for failed calls.
    • Dev Tools: Compatible with TanStack Query Devtools for debugging data-fetching logic.
  2. Understand useGetIdTokenQuery caching and behavior

    main

    The hook manages token lifecycle with the following defaults:

    • Stale Time: 55 minutes (aligned with the 1-hour Firebase token expiration).
    • Garbage Collection: 60 minutes.
    • Query Key: ["auth", "idToken", user.uid, forceRefresh]

    Key Behaviors:

    • Automatic Disabling: The hook automatically disables itself if the user is null.
    • Conditional Fetching: You can use the standard TanStack Query enabled option to control when the token is fetched (e.g., enabled: !!user && needsToken).
    • Side Effects: When reacting to token changes (like updating an API client), use useEffect rather than deprecated TanStack Query callbacks like onSuccess.
  3. Manage connection and on-disconnect operations

    main

    The library provides specialized hooks for managing client connectivity and queuing operations for when a client disconnects:

    • Connectivity: Use useGoOnlineMutation or useGoOfflineMutation (passing the Database instance) to control client connectivity.
    • On-Disconnect: Use useOnDisconnect* hooks (e.g., useOnDisconnectSetMutation, useOnDisconnectRemoveMutation) to queue writes that will only execute when the client disconnects from the Realtime Database server.
  4. How to execute Data Connect Queries

    main

    The generated SDK provides two patterns for executing queries:

    1. Action Shortcut Functions: Direct functions that return a QueryPromise. These are the simplest way to execute a query. They can be called with or without an explicit DataConnect instance.
    2. Query Reference Functions: Functions that return a QueryRef. This reference can then be passed to executeQuery() to obtain a QueryPromise.

    For both methods, if the query requires variables, you must pass an object containing those variables. If no DataConnect instance is provided to the function, the SDK automatically uses the default configuration from the generated package.

    // Pattern 1: Action Shortcut
    const { data } = await listMovies();
    
    // Pattern 2: Query Reference
    import { executeQuery } from 'firebase/data-connect';
    import { listMoviesRef } from '@dataconnect/default-connector';
    
    const ref = listMoviesRef();
    const { data } = await executeQuery(ref);
  5. How mutations work in Data Connect

    main

    Mutations in the generated Web SDK follow two patterns:

    1. Action Shortcut Function: Returns a MutationPromise immediately. This is the simplest way to trigger a mutation.
    2. Mutation Reference Function: Returns a MutationRef. This reference is then passed to executeMutation() to perform the actual operation.

    Key Behaviors:

    • Resolution: The MutationPromise resolves to the result of the mutation once execution is complete.
    • Arguments: If a mutation requires variables, both the shortcut and the ref function accept a single object containing those variables.
    • DataConnect Instance: Both patterns allow passing an optional DataConnect instance. If omitted, the SDK automatically calls getDataConnect(connectorConfig) using the provided configuration.
  6. Realtime Subscription Hooks in TanStack Query Firebase

    main

    TanStack Query Firebase provides realtime event subscription hooks for Firebase services. For Realtime Database, you can use hooks like useOnValueQuery and various useOnChild* hooks, which are available via the @tanstack-query-firebase/react/database subpath. Unlike the legacy version, these hooks correctly handle re-subscriptions when components re-mount.

    // Example subpath for Realtime Database hooks
    import { useOnValueQuery } from '@tanstack-query-firebase/react/database';
  7. Import Realtime Database hooks

    main

    Hooks for the Realtime Database are exported from the database namespace of the @tanstack-query-firebase/react/database package. Hooks typically accept a DatabaseReference or Query (created via ref() or query()) and require a unique queryKey for TanStack Query caching.

    import { useOnValueQuery } from "@tanstack-query-firebase/react/database";
  8. Mutate data in Firebase Data Connect

    main

    You can perform mutations in Firebase Data Connect using two primary methods:

    1. Generated Injectors: Use the specific injector functions generated by your Data Connect schema (e.g., injectCreateMovie). This is the simplest way as it is pre-typed.
    2. injectDataConnectMutation: Use this generic injector if you need more control or are working with a manual reference. It accepts the Data Connect mutation reference as its first argument.

    You can also provide a custom factory function to injectDataConnectMutation to define a custom mutationFn that wraps the Data Connect reference.

    import { injectCreateMovie } from "@firebasegen/movies/angular";
    
    // Using generated injector
    createMovie = injectCreateMovie();
    createMovie.mutate({
      title: 'John Wick',
      genre: "Action",
      imageUrl: "https://example.com/image.jpg",
    });
    
    // Using generic injector with a custom factory function
    import { injectDataConnectMutation } from '@tanstack-query-firebase/angular/data-connect';
    
    createMovie = injectDataConnectMutation(undefined, () => ({
        mutationFn: (title: string) => createMovieRef({ title, reviewDate: Date.now() })
    }));
    
    createMovie.mutate("John Wick");
  9. Query data from Firebase Data Connect in Angular

    main

    To query data from Firebase Data Connect, use the generated injectors (e.g., injectListMyPosts) or the injectDataConnectQuery injector. These injectors automatically handle query key creation and infer the data types and variables required for the query.

    Example usage in an Angular component:

    import { injectListMyPosts } from '@firebasegen/posts/angular'
    
    @Component({
      ... 
      template: `
        @if (movies.isPending()) {
            Loading...
        }
        @if (movies.error()) {
            An error has occurred: {{ movies.error() }}
        }
        @if (movies.data(); as data) {
            @for (movie of data.movies; track movie.id) {
            <mat-card appearance="outlined">
                <mat-card-content>{{movie.description}}</mat-card-content>
            </mat-card>
            } @empty {
                <h2>No items!</h2>
            }
        }
      `,
    })
    export class PostListComponent {
      // The injector automatically creates the query key and infers types
      movies = injectListMyPosts();
    }
    import { injectListMyPosts } from '@firebasegen/posts/angular'
    
    @Component({
      ... 
      template: `
        @if (movies.isPending()) {
            Loading...
        }
        @if (movies.error()) {
            An error has occurred: {{ movies.error() }}
        }
        @if (movies.data(); as data) {
            @for (movie of data.movies; track movie.id) {
            <mat-card appearance="outlined">
                <mat-card-content>{{movie.description}}</mat-card-content>
            </mat-card>
            } @empty {
                <h2>No items!</h2>
            }
        }
      `,
    })
    export class PostListComponent {
      movies = injectListMyPosts();
    }