-1

I have two functions using Axios to make requests of data to an API. Within those functions I also return the response that is received by the API. Both of those functions do use async/await. From my understanding, since Axios returns a promise, I have to await the function calls to unpack the promise and retrieve the data. Am I calling these functions incorrectly? The goal is to use the values returned by these functions to render a React-Leaflet component. However, React it seems to be reading the values as null. My assumption is that when the functions are called within the component, the Promise is being returned instead of the value, even though I am calling the function with await. Below is the code I have so far...

import { MapContainer, TileLayer, Marker, Popup, Circle } from 'react-leaflet';
import mockData from './testData/MOCK_DATA.json'
import axios from 'axios';
import outageData from './testData/outageData.json'

const fillBlueOptions = { fillColor: 'blue' };

async function convertAddressLat(location){//uses the geocodify api to get lat and long of an address
        const resp = await axios.get('https://api.geocodify.com/v2/geocode/json?api_key=mykey&q=' + location.outage_street+ ', ' + location.outage_city+ ', Michigan, USA');
        return Number(resp.data.response.bbox[1]);

}

async function convertAddressLong(location){
    const resp = await axios.get('https://api.geocodify.com/v2/geocode/json?api_key=mykey&q=' + location.outage_street+ ', ' + location.outage_city+ ', Michigan, USA');
    return Number(resp.data.response.bbox[2]);
}

/// This returns the actual value I am looking to pass into the react component
(async function(){
    let val = await convertAddressLat(outageData.outages[6]);
    console.log("Inside function 1: "+val);
})();

//this is still returning a promise. I have tried returning the await and also the method below
console.log("inside function 2: "+(async function(){
    let x = await convertAddressLat(outageData.outages[6]);
    return x;
})());

function OutageMap() { //This is where the map page will be rendered.
    return (
        <MapContainer center={[38.89, -77.059]} zoom={13} scrollWheelZoom={true}>
            <TileLayer
                attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
                url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
            />

            {outageData.outages.map(mock => (
                <Circle center={[
                    (async function(){
                        let lat = await convertAddressLat(mock);
                        return lat;})(), 
                    (async function(){
                        let long = await convertAddressLong(mock);
                        return long;})()
                    ]} 
                    pathOptions={fillBlueOptions} radius={200}>
                    <Popup>{mock.description}</Popup>
                </Circle>
            ))}
        </MapContainer>
    );
  }
  
  export default OutageMap;

I have referenced this post How to return the response from an asynchronous call on how unwrap a promise, but I am confused how I can call the function within the React component.

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
Shafi Kamal
  • 91
  • 3
  • 10
  • The `OutageMap` component looks to be rendering the imported `outageData` JSON. The axios calls seem completely superfluous and unused. It's unclear what you are trying to do and what you think the issue is. – Drew Reese Oct 10 '21 at 17:33
  • 1
    Also, the render of a React component is 100% synchronous and pure, there should be no unintentional side-effects like waiting for code to run. Use the component lifecycle (i.e. `useEffect`, etc...) for this purpose. – Drew Reese Oct 10 '21 at 17:46
  • Could you explain a little further? Currently outageData does not have lat and long values that are needed by Leaflet. Using the locations that are in outageData, I call the functions with the Axios requests to get lat and long values for each location in outageData. – Shafi Kamal Oct 10 '21 at 17:56

3 Answers3

1

Put those functions inside the component. Remove async/await and replace it with then, catch block. For example if we had async function 'fetch' returning the value it would look like:

fetch().then(response => {
   do sth
}).catch(err => {
   console.log(err);
})

Invoke the function in

useEffect(() => {
    fetch().then().catch()
},[]) 

hook. If you pass empty dependencies list it will behave like componentDidMount. In then block assign returned values to state (it will rerender the component).

useEffect(() => {
    fetch().then(res => {
        setState(res.data);
    }).catch()
},[]) 

In the view check if your state has the values: if not render 'No values or sth'. You can also add loading feedback. You set loading in state to true before invoking async function and set to false in then/catch block.

useEffect(() => {
    setLoading(true);
    fetch().then(res => {
        setState(res.data);
        setLoading(false);
    }).catch(err => {
        setLoading(false);
    })
},[]) 

Then you can provide some feedback based on loading state.

0

async functions always return a Promise. So in this block you're awaiting the convertAddressLat Promise but the anonymous inline function also returns a Promise.

center={[
  (async function(){
    let lat = await convertAddressLat(mock);
    return lat;})()

One common strategy for rendering asynchronously loaded data is to do the fetching in a useEffect hook that sets state once the Promise is resolved:

function OutageIndicator ({ outage }) {
    const [coords, setCoords] = useState();

    useEffect(() => {
        async function resolveLocation() {
          const resp = await axios.get( ... );
          const [lat, lng] = resp.data.response.bbox;
          setCoords({ lat, lng })
        }

        resolveLocation();
    }, [outage]);

    return !coords ? "Loading" : (
        <Circle center={[coords.lat, coords.lng]} />
    )
}

function OutageMap () {
    return (
        <MapContainer center={[38.89, -77.059]} zoom={13} scrollWheelZoom={true}>
            {outageData.outages.map(outage => (
                <OutageIndicator outage={outage} />
            )}
        </MapContainer>
    )
}
ray
  • 26,557
  • 5
  • 28
  • 27
-1

As i see that you are currently using functional components. So you can use 'useEffect' hook in that function to call api for example

function myFuncComponent() {
  const [data,setData] = useState();
  useEffect(async () => {
   const { data } = await axios.get('my url');
   setData(data);
  })
}

If you want to call these async calls in a react component you simply have to define a state variable which your component changes that and you will handle the change with a async function or in a useEffect.

  • You can't pass an async callback to useEffect directly because it will return a promise which react will try to use for cleanup. But, you *can* declare and call an async function within an useEffect. see: [React Hook Warnings for async function in useEffect: useEffect function must return a cleanup function or nothing](https://stackoverflow.com/questions/53332321/react-hook-warnings-for-async-function-in-useeffect-useeffect-function-must-ret) – pilchard Oct 10 '21 at 20:38