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.