FirebaseExtended / reactfire

Hooks, Context Providers, and Components that make it easy to interact with Firebase.

Home Page:https://firebaseopensource.com/projects/firebaseextended/reactfire/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Multiple function calls when using custom hook useCallableFunctionResponse

VBota1 opened this issue · comments

Short description

Using the useCallableFunctionResponse hook from reactfire causes the function to be called twice.
This might lead to undesired costs.

Version info

React: "^18"
Firebase: "^10.0.0"
ReactFire: "^4.2.3"
Next: "14.1.0"
Node: "^20"

Test case

Content of app/layout.tsx:

    "use client"
    
    import { Inter } from "next/font/google";
    import "./globals.css";
    import {FirebaseAppProvider} from "reactfire";
    
    const inter = Inter({ subsets: ["latin"] });
    
    const firebaseConfig = {
      // your firebase config
    };
    
    export default function RootLayout({
      children,
    }: Readonly<{
      children: React.ReactNode;
    }>) {
      return (
        <html lang="en">
          <FirebaseAppProvider firebaseConfig={firebaseConfig}>
            <body className={inter.className}>{children}</body>
          </FirebaseAppProvider>
        </html>
      );
    }

Content of app/page.tsx:

    "use client"
    
    import {FunctionsProvider, useCallableFunctionResponse, useFirebaseApp} from "reactfire";
    import {getFunctions} from "@firebase/functions";
    import Test from "@/app/test";
    
    const FUNCTIONS_REGION = 'your region';
    
    export default function Home() {
      const firebaseApp = useFirebaseApp(); 
      const functions = getFunctions(firebaseApp, FUNCTIONS_REGION);
    
      return (
        <FunctionsProvider sdk={functions}>
            <main className="flex min-h-screen flex-col items-center justify-between p-24">
              <Test /> 
            </main>
        </FunctionsProvider>
      );
    }

Content of app/test.tsx:

    import {useCallableFunctionResponse} from "reactfire";

    export default function Test() {
      const responseObservable = useCallableFunctionResponse<any, string>('your_callable_function', {data: {}});
    
      return (
        <div>
          {
            responseObservable.status === 'loading' ?
              <p>Loading ...</p> :
              <p>{responseObservable.data}</p>
          }
        </div>
      );
    }

Steps to reproduce

  • Run app (possible with npm run dev)
  • Open app in browser
  • In Firebase console -> Functions select your_callable_function and open the Detailed usage stats using the 3 dot menu.
    This will open the Google Cloud Console -> Function details page for the function.
  • Select the Logs tab and wait for the logs to load.
  • Check how many times the function was called when you refreshed the browser page.

Expected behavior

The function should be called once.

202x-xx-xx xx:xx:xx.731 xxx | POST 200 xxx B x ms xxxxxx  https://region-project.cloudfunctions.net/your_callable_function 

Actual behavior

The function is actually called twice.

202x-xx-xx xx:xx:xx.731 xxx | POST 200 xxx B x ms xxxxxx  https://region-project.cloudfunctions.net/your_callable_function
202x-xx-xx xx:xx:xx.798 xxx | POST 200 xxx B x ms xxxxxx  https://region-project.cloudfunctions.net/your_callable_function  

Workaround

Using the httpsCallable from firebase/functions combined with the native hooks useEffect and useState solves the issue.

Content of app/test.tsx:

    import {useFunctions} from "reactfire";
    import {httpsCallable} from "firebase/functions";
    import {useEffect, useState} from "react";
    
    export default function Test() {
      const functions = useFunctions();
      const dataReader = httpsCallable<object, string>(functions, 'your_callable_function');
    
      const [responseObservable, setResponseObservable] =
        useState<{status: string, data: string}>({status: 'loading', data: ''});
    
      useEffect(() => {
        dataReader({})
          .then((result) => {
            setResponseObservable({status: 'success', data: result.data});
          })
          .catch((error) => {
            setResponseObservable({status: 'error', data: error});
          });
      }, []);
    
      return (
        <div>
          {
            responseObservable.status === 'loading' ?
              <p>Loading ...</p> :
              <p>{responseObservable.data}</p>
          }
        </div>
      );
    }
commented

try google react useeffect called twice