From dae1e61490e8a94d6f43bda2bf16083a6de2e58b Mon Sep 17 00:00:00 2001
From: jhuleatt <3759507+jhuleatt@users.noreply.github.com>
Date: Fri, 10 Sep 2021 09:58:42 -0400
Subject: [PATCH 1/5] add useCallableFunctionResponse
---
docs/reference/README.md | 1 +
docs/reference/modules/functions.md | 39 +++++++++++++++++++++++++++++
docs/reference/modules/index.md | 7 ++++++
docs/use.md | 17 +++++++++++--
src/functions.tsx | 28 +++++++++++++++++++++
src/index.ts | 1 +
test/functions.test.tsx | 18 +++++++++++--
7 files changed, 107 insertions(+), 4 deletions(-)
create mode 100644 docs/reference/modules/functions.md
create mode 100644 src/functions.tsx
diff --git a/docs/reference/README.md b/docs/reference/README.md
index a206dabf..ea501d1a 100644
--- a/docs/reference/README.md
+++ b/docs/reference/README.md
@@ -11,6 +11,7 @@ ReactFire reference docs
- [database](modules/database.md)
- [firebaseApp](modules/firebaseApp.md)
- [firestore](modules/firestore.md)
+- [functions](modules/functions.md)
- [index](modules/index.md)
- [performance](modules/performance.md)
- [remote-config](modules/remote_config.md)
diff --git a/docs/reference/modules/functions.md b/docs/reference/modules/functions.md
new file mode 100644
index 00000000..2e8bb795
--- /dev/null
+++ b/docs/reference/modules/functions.md
@@ -0,0 +1,39 @@
+[ReactFire reference docs](../README.md) / functions
+
+# Module: functions
+
+## Table of contents
+
+### Functions
+
+- [useCallableFunctionResponse](functions.md#usecallablefunctionresponse)
+
+## Functions
+
+### useCallableFunctionResponse
+
+▸ **useCallableFunctionResponse**<`RequestData`, `ResponseData`\>(`functionName`, `options?`): [`ObservableStatus`](../interfaces/useObservable.ObservableStatus.md)<`ResponseData`\>
+
+Calls a callable function.
+
+#### Type parameters
+
+| Name |
+| :------ |
+| `RequestData` |
+| `ResponseData` |
+
+#### Parameters
+
+| Name | Type | Description |
+| :------ | :------ | :------ |
+| `functionName` | `string` | The name of the function to call |
+| `options?` | [`ReactFireOptions`](../interfaces/index.ReactFireOptions.md)<`ResponseData`\> & { `data?`: `RequestData` ; `httpsCallableOptions?`: `HttpsCallableOptions` } | |
+
+#### Returns
+
+[`ObservableStatus`](../interfaces/useObservable.ObservableStatus.md)<`ResponseData`\>
+
+#### Defined in
+
+src/functions.tsx:13
diff --git a/docs/reference/modules/index.md b/docs/reference/modules/index.md
index a23b5d59..73106a7d 100644
--- a/docs/reference/modules/index.md
+++ b/docs/reference/modules/index.md
@@ -36,6 +36,7 @@
- [useAnalytics](index.md#useanalytics)
- [useAppCheck](index.md#useappcheck)
- [useAuth](index.md#useauth)
+- [useCallableFunctionResponse](index.md#usecallablefunctionresponse)
- [useDatabase](index.md#usedatabase)
- [useDatabaseList](index.md#usedatabaselist)
- [useDatabaseListData](index.md#usedatabaselistdata)
@@ -277,6 +278,12 @@ Re-exports: [useAuth](sdk.md#useauth)
___
+### useCallableFunctionResponse
+
+Re-exports: [useCallableFunctionResponse](functions.md#usecallablefunctionresponse)
+
+___
+
### useDatabase
Re-exports: [useDatabase](sdk.md#usedatabase)
diff --git a/docs/use.md b/docs/use.md
index 0b27157b..dc7e831b 100644
--- a/docs/use.md
+++ b/docs/use.md
@@ -14,7 +14,8 @@
* [Show a single document](#show-a-single-document)
* [Show a list of data (collection)](#show-a-list-of-data-collection)
- [Cloud Functions](#cloud-functions)
- * [Call a function](#call-a-function)
+ * [Call a function based on user interaction](#call-a-function-based-on-user-interaction)
+ * [Call a function on render](#call-a-function-on-render)
- [Realtime Database](#realtime-database)
* [Show an object](#show-an-object)
* [Show a list of data](#show-a-list-of-data)
@@ -304,7 +305,7 @@ function FavoriteAnimals() {
The following samples assume that `FirebaseAppProvider` and `FunctionsProvider` components exist higher up the component tree.
-### Call a function
+### Call a function based on user interaction
```jsx
function Calculator() {
@@ -327,6 +328,18 @@ function Calculator() {
}
```
+### Call a function on render
+
+If you want to call a function when a component renders, instead of in response to user interaction, you can use the `useCallableFunctionResponse` hook.
+
+```jsx
+function LikeCount({ videoId }) {
+ const { status, data: likeCount } = useCallableFunctionResponse('capitalizeText', { data: { videoId: videoId } });
+
+ return This video has {status === 'loading' ? '...' : likeCount} views;
+}
+```
+
## Realtime Database
The following samples assume that `FirebaseAppProvider` and `RealtimeDatabaseProvider` components exist higher up the component tree.
diff --git a/src/functions.tsx b/src/functions.tsx
new file mode 100644
index 00000000..e859b463
--- /dev/null
+++ b/src/functions.tsx
@@ -0,0 +1,28 @@
+import { httpsCallable as rxHttpsCallable } from 'rxfire/functions';
+import { ReactFireOptions, useObservable, ObservableStatus } from './';
+import { useFunctions } from '.';
+
+import type { HttpsCallableOptions } from 'firebase/functions';
+
+/**
+ * Calls a callable function.
+ *
+ * @param functionName - The name of the function to call
+ * @param options
+ */
+export function useCallableFunctionResponse(
+ functionName: string,
+ options?: ReactFireOptions & {
+ httpsCallableOptions?: HttpsCallableOptions;
+ data?: RequestData;
+ }
+): ObservableStatus {
+ const functions = useFunctions();
+ const observableId = `functions:callableResponse:${functionName}:${JSON.stringify(options?.data)}:${JSON.stringify(options?.httpsCallableOptions)}`;
+ const obsFactory = rxHttpsCallable(functions, functionName, options?.httpsCallableOptions);
+
+ //@ts-expect-error because RxFire doesn't make data optional. Remove when https://github.com/FirebaseExtended/rxfire/pull/34 is released.
+ const observable$ = obsFactory(options?.data);
+
+ return useObservable(observableId, observable$, options);
+}
diff --git a/src/index.ts b/src/index.ts
index ddc322a7..ab60a1fe 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -52,6 +52,7 @@ export * from './auth';
export * from './database';
export * from './firebaseApp';
export * from './firestore';
+export * from './functions';
export * from './performance';
export * from './remote-config';
export * from './storage';
diff --git a/test/functions.test.tsx b/test/functions.test.tsx
index 79fb08a6..21c399c6 100644
--- a/test/functions.test.tsx
+++ b/test/functions.test.tsx
@@ -1,9 +1,10 @@
import { initializeApp } from 'firebase/app';
import { getFunctions, connectFunctionsEmulator, httpsCallable } from 'firebase/functions';
import { FunctionComponent } from 'react';
-import { FirebaseAppProvider, FunctionsProvider, useFunctions } from '..';
+import { FirebaseAppProvider, FunctionsProvider, useFunctions, useCallableFunctionResponse } from '..';
import { baseConfig } from './appConfig';
import { renderHook } from '@testing-library/react-hooks';
+import { randomString } from './test-utils';
import * as React from 'react';
describe('Functions', () => {
@@ -25,7 +26,7 @@ describe('Functions', () => {
expect(functionsInstance).toBeDefined();
// `capitalizeText` function is in `functions/index.js`
- const capitalizeTextRemoteFunction = httpsCallable(functionsInstance, 'capitalizeText');
+ const capitalizeTextRemoteFunction = httpsCallable<{ text: string }, string>(functionsInstance, 'capitalizeText');
const testText = 'Hello World';
const { data: capitalizedText } = await capitalizeTextRemoteFunction({ text: testText });
@@ -33,4 +34,17 @@ describe('Functions', () => {
expect(capitalizedText).toEqual(testText.toUpperCase());
});
});
+
+ describe('useCallableFunctionResponse', () => {
+ it('calls a function on render', async () => {
+ const testText = randomString();
+ const { result, waitFor } = renderHook(() => useCallableFunctionResponse<{ text: string }, string>('capitalizeText', { data: { text: testText } }), {
+ wrapper: Provider,
+ });
+
+ await waitFor(() => result.current.status === 'success');
+
+ expect(result.current.data).toEqual(testText.toUpperCase());
+ });
+ });
});
From 64dbc000b77dd3d7f7380aaf42452ab73f1b8ea9 Mon Sep 17 00:00:00 2001
From: jhuleatt <3759507+jhuleatt@users.noreply.github.com>
Date: Fri, 10 Sep 2021 10:00:20 -0400
Subject: [PATCH 2/5] update sample
---
docs/use.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/use.md b/docs/use.md
index dc7e831b..c18bbe6a 100644
--- a/docs/use.md
+++ b/docs/use.md
@@ -334,7 +334,7 @@ If you want to call a function when a component renders, instead of in response
```jsx
function LikeCount({ videoId }) {
- const { status, data: likeCount } = useCallableFunctionResponse('capitalizeText', { data: { videoId: videoId } });
+ const { status, data: likeCount } = useCallableFunctionResponse('countVideoLikes', { data: { videoId: videoId } });
return This video has {status === 'loading' ? '...' : likeCount} views;
}
From fb79788c1abf8a639edae870fafc063f58be88d2 Mon Sep 17 00:00:00 2001
From: Jeff <3759507+jhuleatt@users.noreply.github.com>
Date: Fri, 10 Sep 2021 14:06:51 +0000
Subject: [PATCH 3/5] fix docs
---
docs/reference/modules/functions.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/reference/modules/functions.md b/docs/reference/modules/functions.md
index 2e8bb795..079f5fe7 100644
--- a/docs/reference/modules/functions.md
+++ b/docs/reference/modules/functions.md
@@ -36,4 +36,4 @@ Calls a callable function.
#### Defined in
-src/functions.tsx:13
+[src/functions.tsx:13](https://github.com/FirebaseExtended/reactfire/blob/main/src/functions.tsx#L13)
From 256634beed4f563b0afa515b02f96d24ebb52215 Mon Sep 17 00:00:00 2001
From: Jeff <3759507+jhuleatt@users.noreply.github.com>
Date: Fri, 10 Sep 2021 14:08:18 +0000
Subject: [PATCH 4/5] fix typo
---
docs/use.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/use.md b/docs/use.md
index c18bbe6a..6068e80d 100644
--- a/docs/use.md
+++ b/docs/use.md
@@ -336,7 +336,7 @@ If you want to call a function when a component renders, instead of in response
function LikeCount({ videoId }) {
const { status, data: likeCount } = useCallableFunctionResponse('countVideoLikes', { data: { videoId: videoId } });
- return This video has {status === 'loading' ? '...' : likeCount} views;
+ return This video has {status === 'loading' ? '...' : likeCount} likes;
}
```
From c6d6bcdf228e8c6b621d58d3e0c4f863a2d52801 Mon Sep 17 00:00:00 2001
From: jhuleatt <3759507+jhuleatt@users.noreply.github.com>
Date: Fri, 10 Sep 2021 10:17:06 -0400
Subject: [PATCH 5/5] add to example app
---
example/withoutSuspense/Functions.tsx | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/example/withoutSuspense/Functions.tsx b/example/withoutSuspense/Functions.tsx
index 7801889c..5ee1038b 100644
--- a/example/withoutSuspense/Functions.tsx
+++ b/example/withoutSuspense/Functions.tsx
@@ -1,7 +1,7 @@
import 'firebase/storage';
import * as React from 'react';
import { useState } from 'react';
-import { useFirebaseApp, FunctionsProvider, useFunctions } from 'reactfire';
+import { useFirebaseApp, FunctionsProvider, useFunctions, useCallableFunctionResponse } from 'reactfire';
import { CardSection } from '../display/Card';
import { LoadingSpinner } from '../display/LoadingSpinner';
import { WideButton } from '../display/Button';
@@ -13,11 +13,12 @@ function UpperCaser() {
const [uppercasedText, setText] = useState('');
const [isUppercasing, setIsUppercasing] = useState(false);
+ const greetings = ['Hello World', 'yo', `what's up?`];
+ const textToUppercase = greetings[Math.floor(Math.random() * greetings.length)];
+
async function handleButtonClick() {
setIsUppercasing(true);
- const greetings = ['Hello World', 'yo', `what's up?`];
- const textToUppercase = greetings[Math.floor(Math.random() * greetings.length)];
const { data: capitalizedText } = await capitalizeTextRemoteFunction({ text: textToUppercase });
setText(capitalizedText);
@@ -27,11 +28,23 @@ function UpperCaser() {
return (
<>
- {isUppercasing ? : {uppercasedText}}
+ {isUppercasing ? : {uppercasedText || `click the button to capitalize "${textToUppercase}"`}}
>
);
}
+function UpperCaserOnRender() {
+ const greetings = ['Hello World', 'yo', `what's up?`];
+ const textToUppercase = greetings[Math.floor(Math.random() * greetings.length)];
+ const { status, data: uppercasedText } = useCallableFunctionResponse<{ text: string }, string>('capitalizeText', { data: { text: textToUppercase } });
+
+ if (status === 'loading') {
+ return ;
+ }
+
+ return {uppercasedText};
+}
+
export function Functions() {
const app = useFirebaseApp();
@@ -40,6 +53,9 @@ export function Functions() {
+
+
+
);
}