And in my react component, why cant I do this?
You can call it, but you won't want to, because you won't be able to use its result. The render call (the call to your component function) is synchronous, and your async
function reports its completion asynchronously.
Separately, React will call your component's function to render whenever it needs to, so depending on what the function does, if you did call it directly during render, you might be calling it more often than you should. This is why you use useEffect
to control when you call it:
- With no dependency array: Called after every render (relatively uncommon).
- With an empty dependency array: Called after the first render only, and never again for the same element.
- With a non-empty dependency array: Called after the first render and then called again after any render where any of the dependency values changed.
Is there another way or is this just how it works?
This is just now it works, render calls are synchronous.
It's not clear to me what getFactoryPair
does. If it's just accessing information, it would be fine to call it during render (if it were synchronous), but if it's creating or modifying information, it wouldn't be, because rendering should be pure other than state and such handled via hooks.
In a comment you've clarified what you use the function for:
...getFactoryPair
basically retrieves the contract address for another contract based on the given index. Simply retrieving information in an async way. I would eventually use the results to display it on a table component.
Assuming you get myContractInstance
as a prop (though I may be wrong there, you didn't show it as one), you'd probably use state to track whether you had received the pair, and useEffect
to update the pair if myContractInstance
changed:
const App: React.FC<PropsTypeHere> = ({ myContractInstance }) => {
const [pair, setPair] = useState<string | null>(null);
const [pairError, setPairError] = useState<Error | null>(null);
useEffect(() => {
getFactoryPair(myContractInstance, 1)
.then((pair) => {
setPair(pair);
setPairError(null);
})
.catch((error) => {
setPair(null);
setPairError(error);
});
}, [myContractInstance]);
if (pairError !== null) {
// Render an "error" state
return /*...*/;
}
if (pair === null) {
// Render a "loading" state
return /*...*/;
}
// Render the table element using `pair`
return /*...*/;
};
This is a fairly classic use of useEffect
. You can wrap it up in a custom, reusable hook if you like:
type UsePairResult =
// The pair is loaded
| ["loaded", string, null]
// The pair is loading
| ["loading", null, null]
// There was an error loading
| ["error", null, string];
function usePair(instance: TheInstanceType, index: number): UsePairResult {
const [pair, setPair] = useState<string | null>(null);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
getFactoryPair(instance, index)
.then((pair) => {
setPair(pair);
setError(null);
})
.catch((error) => {
setPair(null);
setError(error);
});
}, [instance, index]);
const state = pair ? "loaded" : error ? "error" : "loading";
return [state, pair, error];
}
Then any place you need it:
const App = ({ myContractInstance }) => {
const [pairState, pair, pairError] = usePair(myContractInstance, 1);
switch (pairState) {
case "loaded":
// Render the table element using `pair`
return /*...*/;
case "loading":
// Render a "loading" state
return /*...*/;
case "error":
// Render an "error" state
return /*...*/;
}
};