Home > other >  Can I use an async function outside of useEffect in React functional component?
Can I use an async function outside of useEffect in React functional component?

Time:02-04

Is there any other way to use an async function in a react component outside of useEffect?

For example, I defined an async function outside of the react component:

//In src/utils/getFactoryPair.ts
export const getFactoryPair = async (factoryContract: SwapV2Factory, pairIndex: number): Promise<string> => {
    const pair =  await factoryContract.allPairs(pairIndex);
    return pair;
}

And in my react component, why cant I do this?

import { getFactoryPair } from "src/utils/getFactoryPair.ts";

const App: React.FC = () => {
  ...
  const pair = getFactoryPair(myContractInstance, 1)
  ...
}

And instead I have to call it within useEffect or define another async function within the react component to call getFactoryPair.

Is there another way or is this just how it works?

CodePudding user response:

From What is the point of useEffect() if you don't specify a dependancy:

Effects that are executed at the top-level inside functional component execute in a different way as compared to the effect inside the useEffect hook.

When effects are inside the useEffect:

  1. They are executed after browser has painted the screen, i.e. after React has applied changes to the DOM.
  2. Before running the effect again, useEffect can run the clean-up function, if clean-up function is provided.
  3. Specifying the dependency array will allow you to skip running the effect after every re-render of the component.

Because of the above mentioned points, you always want the side-effects to be executed inside the useEffect hook.

Hope this helps!

CodePudding user response:

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 /*...*/;
    }
};
  • Related