Home > other >  Struggling with async custom react hooks. How do i await result of one hook to use in another hook?
Struggling with async custom react hooks. How do i await result of one hook to use in another hook?

Time:02-04

I'm struggling a bit with using custom react hooks.

I got 2 custom hooks.

First hook is for fetching a ID, the second one is used to fetch a profile with this previous fetched ID. It is dependent on that ID so I need to await this promise.

I have the following custom hook:

export const UseMetamask = () => {

    //Todo: Create check if metamask is in browser, otherwise throw error
    const fetchWallet = async (): Promise<string | null> => {
        try {
            const accounts: string[] = await window.ethereum.request(
                {
                    method: 'eth_requestAccounts'
                },
            );

            return accounts[0];

        } catch(e) {
            console.error(e);

            return null;
        }
    }
    
    return fetchWallet();
}

Then in my second hook I have:

const wallet = UseMetamask();

which is then used in a react-query call like:

useQuery(
    ['user', wallet],
    () => getUserByWallet(wallet),

Now it complains on the wallet about it being a Promise<string | null> which is ofcourse not suitable for the getUserByWallet.

What is the go to way to wait for another hook then use that result in a second hook?

Thanks!

CodePudding user response:

A functional component is a synchronous function, and as a component has life cycle hooks. The asynchronous calls are side effects that should be handled by hooks, not by passing promises in the body of the function. See this SO answer.

Option 1 - using useEffect with useState:

Wrap the api call in useEffect and set the wallet state when the api call succeeds. Return the wallet state from the hook:

export const useMetamask = () => {
  const [wallet, setWallet] = useState<string | null>(null);
  
  useEffect(() => {
    const fetchWallet = async(): Promise<string | null> => {
      try {
        const accounts: string[] = await window.ethereum.request({
          method: 'eth_requestAccounts'
        });

        setWallet(accounts[0]);

      } catch (e) {
        console.error(e);

        return null;
      }
    }
    
    fetchWallet();
  }, []);

  return wallet;
}

Usage:

Get the wallet from the hook. This would be null or the actual value:

const wallet = useMetamask();

Only enable the call when a wallet actually exists (not null). We'll use the enable option (see Dependent Queries), to enable/disable the query according to the value of wallet:

useQuery(
  ['user', wallet],
  () => getUserByWallet(wallet),
  {
  // The query will not execute until the wallet exists
  enabled: !!wallet,
  }
)

Option 2 - use two useQuery hooks

Since you already use useQuery, you need to manually write a hook. Just get the wallet from another useQuery call:

const wallet useQuery('wallet', fetchWallet);

useQuery(
  ['user', wallet],
  () => getUserByWallet(wallet),
  {
  // The query will not execute until the wallet exists
  enabled: !!wallet,
  }
)

CodePudding user response:

It is a bad idea to create a hook then just return a single function out of it. And it is a promise too on top of that. Return an object from your hook instead. Then await it in your caller.

export const useMetamask = () => {

    //Todo: Create check if metamask is in browser, otherwise throw error
    const fetchWallet = async (): Promise<string | null> => {
        try {
            const accounts: string[] = await window.ethereum.request(
                {
                    method: 'eth_requestAccounts'
                },
            );

            return accounts[0];

        } catch(e) {
            console.error(e);

            return null;
        }
    }
    
    return { fetchWallet };
}

Then in your caller

const { fetchWallet } = useMetamask();
const wallet = await fetchWallet();

useQuery(
    ['user', wallet],
    () => getUserByWallet(wallet),

Also, please use a small letter 'useSomething' in your hooks to differentiate it from your UI components

CodePudding user response:

You need to use useState in the custom hook.

// move fetchWallet function to utils and import it here for better code smells

export const useMetamask = () => {
    const [wallet, setWallet] = useState(null);
    // you do not want to fetch wallet everytime the component updates, You want to do it only once.
    useEffect(()=>{
        fetchWallet().then(wallet => setWallet(wallet)).catch(errorHandler);
    }, [])
    return wallet;
}

In other hooks, check if wallet is null and handle accordingly.

  • Related