react-firebase-hooks icon indicating copy to clipboard operation
react-firebase-hooks copied to clipboard

Feature proposal : enable query to be a Promise<Query> in `database/useObject` and similar hooks

Open rpelissier opened this issue 3 years ago • 3 comments

I have a use case that make the use of useObject quite painful. It happens that the path of the Query is depending on another request to the Database. As conditional Hooks are forbidden I have to create every time another layer of Component to do the first query before I can use the secundary Component with your hook. It would be great if query can optionaly be a Promise<Query> so I don't need this extra layer.

Example code :

import { useEffect, useMemo, useState } from 'react';
import { snapshotToData, ValOptions } from './helpers';
import { ObjectHook, ObjectValHook, Val } from './types';
import { useIsEqualRef, useLoadingValue } from '../util';
import { DataSnapshot, off, onValue, Query } from 'firebase/database';

function isPromise(p: any): p is Promise<Query | null | undefined> {
  return typeof p === 'object' && typeof p['then'] === 'function';
}

export const useObject = (
  query?: Query | null | Promise<Query | null | undefined>
): ObjectHook => {
  const { error, loading, reset, setError, setValue, value } = useLoadingValue<
    DataSnapshot,
    Error
  >();

  const queryPromise: Promise<Query | null | undefined> = isPromise(query)
    ? query
    : Promise.resolve(query);

  const [resolved, setResolved] = useState(false);

  const ref = useIsEqualRef<Query>(undefined, reset);

  useEffect(() => {
    if (!resolved) {
      queryPromise.then((query) => {
        ref.current = query;
        setResolved(true);
      });
      return;
    }

    const effect = async () => {
      const query = ref.current;

      if (!query) {
        setValue(undefined);
        return;
      }

      onValue(query, setValue, setError);

      return () => {
        off(query, 'value', setValue);
      };
    };

    effect();
  }, [ref.current]);

  const resArray: ObjectHook = [value, loading, error];
  return useMemo(() => resArray, resArray);
};

rpelissier avatar Sep 20 '22 16:09 rpelissier

@rpelissier Apologies for the delay. Can you share the code example where you need to pass a Promise<Query> to useObject?

chrisbianca avatar Nov 04 '22 17:11 chrisbianca

I accomplish this with other hook-based data fetching libraries (like urql) with a disabled flag. So I can have something like:

const [userData, userLoading, userError] = useQuery(blah);
const [otherUserData] = useQuery(query_using_userData_somehow, { disabled: !!userData })

Would be nice to have in this lib too. Working around it with nested components is a pain.

zwily avatar Mar 21 '23 20:03 zwily

would definitely make lives easier when trying to fetch data that is dependant on previously fetched data...

mshahzebraza avatar Apr 28 '23 12:04 mshahzebraza