Skip to content

Commit 8f8fd08

Browse files
authored
add useCallableFunctionResponse (#449)
1 parent a359de4 commit 8f8fd08

File tree

8 files changed

+127
-8
lines changed

8 files changed

+127
-8
lines changed

docs/reference/README.md

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/modules/functions.md

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/modules/index.md

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/use.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
* [Show a single document](#show-a-single-document)
1515
* [Show a list of data (collection)](#show-a-list-of-data-collection)
1616
- [Cloud Functions](#cloud-functions)
17-
* [Call a function](#call-a-function)
17+
* [Call a function based on user interaction](#call-a-function-based-on-user-interaction)
18+
* [Call a function on render](#call-a-function-on-render)
1819
- [Realtime Database](#realtime-database)
1920
* [Show an object](#show-an-object)
2021
* [Show a list of data](#show-a-list-of-data)
@@ -304,7 +305,7 @@ function FavoriteAnimals() {
304305

305306
The following samples assume that `FirebaseAppProvider` and `FunctionsProvider` components exist higher up the component tree.
306307

307-
### Call a function
308+
### Call a function based on user interaction
308309

309310
```jsx
310311
function Calculator() {
@@ -327,6 +328,18 @@ function Calculator() {
327328
}
328329
```
329330

331+
### Call a function on render
332+
333+
If you want to call a function when a component renders, instead of in response to user interaction, you can use the `useCallableFunctionResponse` hook.
334+
335+
```jsx
336+
function LikeCount({ videoId }) {
337+
const { status, data: likeCount } = useCallableFunctionResponse('countVideoLikes', { data: { videoId: videoId } });
338+
339+
return <span>This video has {status === 'loading' ? '...' : likeCount} likes</span>;
340+
}
341+
```
342+
330343
## Realtime Database
331344

332345
The following samples assume that `FirebaseAppProvider` and `RealtimeDatabaseProvider` components exist higher up the component tree.

example/withoutSuspense/Functions.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'firebase/storage';
22
import * as React from 'react';
33
import { useState } from 'react';
4-
import { useFirebaseApp, FunctionsProvider, useFunctions } from 'reactfire';
4+
import { useFirebaseApp, FunctionsProvider, useFunctions, useCallableFunctionResponse } from 'reactfire';
55
import { CardSection } from '../display/Card';
66
import { LoadingSpinner } from '../display/LoadingSpinner';
77
import { WideButton } from '../display/Button';
@@ -13,11 +13,12 @@ function UpperCaser() {
1313
const [uppercasedText, setText] = useState<string>('');
1414
const [isUppercasing, setIsUppercasing] = useState<boolean>(false);
1515

16+
const greetings = ['Hello World', 'yo', `what's up?`];
17+
const textToUppercase = greetings[Math.floor(Math.random() * greetings.length)];
18+
1619
async function handleButtonClick() {
1720
setIsUppercasing(true);
1821

19-
const greetings = ['Hello World', 'yo', `what's up?`];
20-
const textToUppercase = greetings[Math.floor(Math.random() * greetings.length)];
2122
const { data: capitalizedText } = await capitalizeTextRemoteFunction({ text: textToUppercase });
2223
setText(capitalizedText);
2324

@@ -27,11 +28,23 @@ function UpperCaser() {
2728
return (
2829
<>
2930
<WideButton label="Uppercase some text" onClick={handleButtonClick} />
30-
{isUppercasing ? <LoadingSpinner /> : <span>{uppercasedText}</span>}
31+
{isUppercasing ? <LoadingSpinner /> : <span>{uppercasedText || `click the button to capitalize "${textToUppercase}"`}</span>}
3132
</>
3233
);
3334
}
3435

36+
function UpperCaserOnRender() {
37+
const greetings = ['Hello World', 'yo', `what's up?`];
38+
const textToUppercase = greetings[Math.floor(Math.random() * greetings.length)];
39+
const { status, data: uppercasedText } = useCallableFunctionResponse<{ text: string }, string>('capitalizeText', { data: { text: textToUppercase } });
40+
41+
if (status === 'loading') {
42+
return <LoadingSpinner />;
43+
}
44+
45+
return <span>{uppercasedText}</span>;
46+
}
47+
3548
export function Functions() {
3649
const app = useFirebaseApp();
3750

@@ -40,6 +53,9 @@ export function Functions() {
4053
<CardSection title="Call a cloud function">
4154
<UpperCaser />
4255
</CardSection>
56+
<CardSection title="Call a function on render">
57+
<UpperCaserOnRender />
58+
</CardSection>
4359
</FunctionsProvider>
4460
);
4561
}

src/functions.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { httpsCallable as rxHttpsCallable } from 'rxfire/functions';
2+
import { ReactFireOptions, useObservable, ObservableStatus } from './';
3+
import { useFunctions } from '.';
4+
5+
import type { HttpsCallableOptions } from 'firebase/functions';
6+
7+
/**
8+
* Calls a callable function.
9+
*
10+
* @param functionName - The name of the function to call
11+
* @param options
12+
*/
13+
export function useCallableFunctionResponse<RequestData, ResponseData>(
14+
functionName: string,
15+
options?: ReactFireOptions<ResponseData> & {
16+
httpsCallableOptions?: HttpsCallableOptions;
17+
data?: RequestData;
18+
}
19+
): ObservableStatus<ResponseData> {
20+
const functions = useFunctions();
21+
const observableId = `functions:callableResponse:${functionName}:${JSON.stringify(options?.data)}:${JSON.stringify(options?.httpsCallableOptions)}`;
22+
const obsFactory = rxHttpsCallable<RequestData, ResponseData>(functions, functionName, options?.httpsCallableOptions);
23+
24+
//@ts-expect-error because RxFire doesn't make data optional. Remove when https://github.com/FirebaseExtended/rxfire/pull/34 is released.
25+
const observable$ = obsFactory(options?.data);
26+
27+
return useObservable(observableId, observable$, options);
28+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export * from './auth';
5252
export * from './database';
5353
export * from './firebaseApp';
5454
export * from './firestore';
55+
export * from './functions';
5556
export * from './performance';
5657
export * from './remote-config';
5758
export * from './storage';

test/functions.test.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { initializeApp } from 'firebase/app';
22
import { getFunctions, connectFunctionsEmulator, httpsCallable } from 'firebase/functions';
33
import { FunctionComponent } from 'react';
4-
import { FirebaseAppProvider, FunctionsProvider, useFunctions } from '..';
4+
import { FirebaseAppProvider, FunctionsProvider, useFunctions, useCallableFunctionResponse } from '..';
55
import { baseConfig } from './appConfig';
66
import { renderHook } from '@testing-library/react-hooks';
7+
import { randomString } from './test-utils';
78
import * as React from 'react';
89

910
describe('Functions', () => {
@@ -25,12 +26,25 @@ describe('Functions', () => {
2526
expect(functionsInstance).toBeDefined();
2627

2728
// `capitalizeText` function is in `functions/index.js`
28-
const capitalizeTextRemoteFunction = httpsCallable(functionsInstance, 'capitalizeText');
29+
const capitalizeTextRemoteFunction = httpsCallable<{ text: string }, string>(functionsInstance, 'capitalizeText');
2930
const testText = 'Hello World';
3031

3132
const { data: capitalizedText } = await capitalizeTextRemoteFunction({ text: testText });
3233

3334
expect(capitalizedText).toEqual(testText.toUpperCase());
3435
});
3536
});
37+
38+
describe('useCallableFunctionResponse', () => {
39+
it('calls a function on render', async () => {
40+
const testText = randomString();
41+
const { result, waitFor } = renderHook(() => useCallableFunctionResponse<{ text: string }, string>('capitalizeText', { data: { text: testText } }), {
42+
wrapper: Provider,
43+
});
44+
45+
await waitFor(() => result.current.status === 'success');
46+
47+
expect(result.current.data).toEqual(testText.toUpperCase());
48+
});
49+
});
3650
});

0 commit comments

Comments
 (0)