FirebaseExtended / reactfire

Hooks, Context Providers, and Components that make it easy to interact with Firebase.
https://firebaseopensource.com/projects/firebaseextended/reactfire/
MIT License
3.54k stars 403 forks source link

Feature Request/Proposal: Expanding Functions #447

Closed justjish closed 3 years ago

justjish commented 3 years ago

A few thoughts on expanding the Functions API.

I was thinking if it would be worthwhile to expand the functions API a bit, mainly in regard to handling loading and error states. And potentially expose some helpful options. (I haven't fully thought through the various options, but wanted to start a discussion on them)

// Sorta risk combining types like this, but can't think of a cleaner way to keep things simple. 
type ReactFireHttpsCallableOptions & HttpsCallableOptions = {
   suspense: boolean, // Enable suspense? Maybe just use the internal settings to determine if we should suspend.
   start: boolean, // Calls the functions right away. 
   data?: RequestData // Firebase Typing, To use in conjunction with start option
   warmup: boolean // See Below
}
type Status = {
loading: boolean
error: FunctionsError // Firebase Typing
}
const [data, send, status] = useHttpsCallable<Req,Res>('calculate', {}:ReactFireHttpsCallableOptions) ;

ReactFireHttpsCallableOptions Breakdown

useHttpsCallable / useFunctionsCallable / useFunctionsHttpsCallable

Example Use Cases

Simple

const [data, send, {loading, error} ] = useHttpsCallable('MyFunctions');

if (loading) return <LoadingComponent/>
if (error) return <ErrorFallback {...error}/>
return <button onClick={()=> send('my payload')}> Start </button>

Call function right away.

const [data, send, {loading, error} ] = useHttpsCallable('MyFunctions', { start: true, data: props.id });

if (loading) return <LoadingComponent/>
if (error) return <ErrorFallback {...error}/>
return <div>{data.message}</div>
jhuleatt commented 3 years ago

Hey @sujishpatel, cool ideas!

cold boot can be a massive pain point for some

Agreed, but we actually have good news on this front! Check out the new minInstances runtime option for information on how to reduce cold starts. While a warmup built into ReactFire is an interesting idea, now that cold starts are better solved at the platform level, I don't think a ReactFire-specific feature is necessary.


I'm excited about your idea to add convenience functions on top of what #444 offers. Instead of an options object, what do you think about two separate hooks? I think this would make it extra-clear what's happening (and prevent accidentally calling a callable function, for example).

Riffing on your ideas a bit:

new ReactFire hooks:

function useGetCallableFunction<RequestData, ResponseData>(
  functionName: string,
  options?: HttpsCallableOptions
): HttpsCallable<RequestData, ResponseData> {}

function useCallableFunctionResponse<RequestData, ResponseData>(
  functionName: string,
  options?:
    | ReactFireOptions<ResponseData>
    | {
        httpsCallableOptions?: HttpsCallableOptions;
        data?: RequestData;
      }
): ObservableStatus<ResponseData> {}

example:

import { useCallableFunctionResponse, useGetCallableFunction } from "reactfire";

function LikeButton({ videoId }) {
  const saveLike = useGetCallableFunction<
    { videoId: string },
    { liked: boolean }
  >("saveLike");
  const [isLiked, setIsLiked] = useState(false);

  const likeVideo = () => {
    saveLike().then(({ data }) => {
      setIsLiked(data.liked);
    });
  };

  return (
    <button onClick={likeVideo} disabled={isLiked}>
      Like
    </button>
  );
}

function LikeCountIndicator({ videoId }) {
  const { data, status } = useCallableFunctionResponse<
    { videoId: string },
    { count: number }
  >("likeCount", { data: { videoId: videoId } });

  if (status === "loading") {
    return <span>Loading likes...</span>;
  }

  return <span>This video has {data.count} likes</span>;
}

How does that sound to you?