1

Hoping to get some guidance on how to best work with async data calls in React.

I have a page that has a FullCalendar component which will show the events given to it in a state, i.e., simplifying a lot,

return (
    ...
    <FullCalendar events={ events } />
)

What I need to do to get the events is first to make a call to Google Calendar's APIs to fetch the Oauthed user's calendars, then looping through each calendar, make a call to Google calendar to fetch that calendar's events. Something like (very simplified):

let eventsToShow = [];

apiCalendar.listCalendars({}).then(({result}) => {
    // At this point I have the list of the user's calendars
    result.items.forEach((calendar) => {
        // Now I have all the events on a given calendar
        apiCalendar.listEvents(calendar.id).then(({result}) => {
            eventsToShow.push({id: result.id, start: result.start, end: result.end});
        })
    })
})

setEvents(eventsToShow);

Right now, I have a button on the page, and I'd like to kick off the calls to fetch the calendars and events when I click the button (because I have a separate button that does the Oauth sequence). Later, my intent is to store something from the Oauth so that the page will just pull the calendars and events automatically if the token is present.

In the above pseudocode, I have various pieces working - the FullCalendar component can show hard-coded events, my various API calls do fetch the correct date - but I'm having trouble figuring out how to put it all together.

I suspect I have to use a useEffect as well as use await, but the nested nature of the API calls is confusing me. If I do have to use useEffect, I'm also not sure how to get the calls to trigger off a button click for now.

I've tried having an onClick function for the button that just leads to essentially the second block of pseudocode above. This worked for causing the API calls to run (as evidenced by console.logs along the way), but the setEvents part at the end doesn't work, I suspect because of the async nature of things.

Help? Am I completely misguided?

1 Answer 1

1

If what you want is to bind the api calls to the click of the button you wouldn't need a useEffect.

Definitely you can improve the readability of the async calls with the use of async await. From what you shared I get that the async call should be something like this:


const fetchCalendarEvents = async()=>{
  try{
    const calendars = await apiCalendar.listCalendars()
    const events = await Promise.allSettled(calendars.map(c=>{
      return apiCalendar.listEvents(c.id)
    }))
    return events
  }
  catch (error){
    console.error(error)
  }
}

You can add the required parameters that you need to do the api call.

Within the component then you can define the onClick event on the button which will trigger this function something like

const handleEventsFetch = async()=>{
  const events = await fetchCalendarEventts(
    //...params
  )
  setEvents(events)
}

If you ever want the fetching to be on component load you can definitely do that in a useEffect. Note you cant use async-await in useEffect but we can use then/catch or an IIFE

//then
useEffect(()=>{
    fetchCalendarEvents(
      //...
    ).then(
    setEvents
    )
},[])
//iife
useEffect(()=>{
  (async()=>{
    const events = await fetchCalendarEvents(
      //...
    )
    setEvents(events)

  })()
},[])

Note I don't know what the params for the api calls are so beware that you might need to add dependencies to the useEffect. In consequence of that you might also need to add some if checks before running the api call so that you don't run it multiple times if any of the dependencies change

EDIT: Add transformation on returning the events


const fetchCalendarEvents = async () => {
  try {
    const calendars = await apiCalendar.listCalendars();
    //rename this so that it is more descriptive
    const calendarsWithEvents = await Promise.allSettled(
      //make the map function async!
      calendars.map(async (c) => {
        // now you can use await
        const calEvents = await apiCalendar(c.id);
        return {
          calendarId: c.id,
          events: calEvents,
        };
      })
    );
    return calendarsWithEvents;
  } catch (error) {
    console.error(error);
  }
};
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for the explanation. This works! One follow-on question: in your first code block, looking at return apiCalendar.listEvents(c.id), what if I need to annotate the events pulled for each calendar somehow? One quirk about GCal's API is that the event objects returned by listEvents don't have the event's calendar, so I'd like to make sure the resulting events for each cal is associated with the cal somehow. I was considering return {cal: c.id, events: apiCalendar.listEvents(c.id)}, but the resulting value of all the events fields look like promises.
Sure that makes totally sense. Check out I just edited the post, added the transformation. To be able to do that you must await the promise for the events, that you can do easily by declaring the mapper function to be async. That is still yielding a promise after all, so the outside code needs not to be changed. As you said: return {cal: c.id, events: apiCalendar.listEvents(c.id)} returns {cal:number,events:Promise} because at that point the promise was not unwrapped yet, that is what you use await for, you unwrap the promise and either get the value or encounter an error
That does it - thanks very much for your help! Also a good primer on async/await.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.