useState with async function returning Promise {<pending>}

Viewed 22851

So, I know this question has been asked 100's of times, but none of the solutions seems to work in my instance.

I am using useState hook to update state to a value initialValues that gets data returned from a getInitialValues function

const [initialValues, setInitialValues] = useState(getInitialValues());

The getInitialValues function does a logic check and either returns an object or another function retrieveDetails()

const getInitialValues = () => {
   let details;
   if(!addressDetails) {
      details = retrieveDetails();
   } else {
      details = { 
         ...,
         ...,
         ...
      };
   }
   return details;
}

The function, retrieveDetails is an async function that makes an API call, and I await the response and return the object received from the response.

const retrieveDetails = async () => {
   const addr = addressDetails[currentAddress];
   const { addressLookup } = addr;
   const key = process.env.API_KEY;
   const query = `?Key=${key}&Id=${addressLookup}`;
   const addrDetails = await new AddrService().getAddressDetails(query);
   return addrDetails;
}

However, when I log the state initialValues it returns Promise {<pending>}?

Even removing the API call and simple returning an object in it's place renders the same result.

Not sure the best way around this to actually return the object?

Any help would be greatly appreciated.

3 Answers

I don't think there is a way to get initial data to useState asynchronously, at least not yet.

React is not waiting for your data to arrive, the function will keep on running to completion while your async operation is queued (on the event loop side).

The current idiomatic way is to fetch the data in an effect and update the state.

useEffect(() => {
  getData(someParam).then(data => setState(data))
}, [someParam]) 

You can read more about it in the DOCS

This isn't something React's built-in hooks support.

You need to build or import a custom hook.

Want short and simple? Try this:

const addressDetails = useAsyncFunctionResult(retrieveDetails);

and add this in hooks/useAsyncFunctionResults.js

function useAsyncFunctionResult(asyncFunction, dependencies = []) {
  const [result, setResult] = React.useState();

  React.useEffect(() => {
    let mounted = true;
    asyncFunction().then((data) => mounted && setResult(data))
    return () => { mounted = false; }
  }, dependencies]);

  return result;
}

Here the retrieveDetails function (the one from the question) will start executing (the ().then bit above) when the hook is first called. The result is kept until unmount. And we get no errors about changing component state after unmounting.

If you later want to add caching, use existing hooks instead if making your own.

There's no official useAsync in React, and likely never will be, because if your Promise did a request and the requesting component did unmount before the promise resolves, then best practice is to cancel which differs case by case.

const retrieveDetails = async () => {
   const addr = addressDetails[currentAddress];
   const { addressLookup } = addr;
   const key = process.env.API_KEY;
   const query = `?Key=${key}&Id=${addressLookup}`;
   const addrDetails = await Promise.resolve(new AddrService().getAddressDetails(query))
   return addrDetails;
}


**try this once changed the function a bit**
Related