Why does async work in ComponentDidMount lead to visual lag when navigating unless a simple timeOut is awaited?

Viewed 706

I have a tabbed react native app and have 2 related questions:

  1. Why does the navigation between screens visually lag when doing async work (such sending a network request) in the target view's componentDidMount method? The official documentation mentions this method as the appropriate place for handling network requests but even when componentDidMount and the functions it calls are declared to be async () => the visual delay when initiating navigation is glaring. This visual delay occurs at the beginning of the navigation, before the target component is pushed or focused and occurs even when the async work does not modify the app state in any way that would affect the rendering of the target component.

  2. Why does this visual delay disappear entirely when I await a brief timeout before doing the real async work?

const timeOut = t => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`Completed in ${t}`);
    }, t);
  });
};

componentDidMount = async () => { // async or not, delay occurs
  await timeOut(0); //This line prevents navigation lag
  NetworkRequest.getData(payload);
}
...

static getData = payload => {
fetch("API_ENDPOINT", {
      method: "POST",
      body: payload
    })
      .then(response => response.json())
      .then(response => {
              //process response 
       })
}

Whether NetworkRequest.getData is awaited or not, the navigation delay is present without awaiting this artificial mini-promise beforehand. I feel like I'm misunderstanding react native lifecycles or some other key part of the rendering process because I can't imagine why this little await is necessary to 'trick' the method into being visually asynchronous...

EDIT: Now that I know more about Message Queues and Job Queues, it makes sense that a potential source of the visual lag could come from jamming up the Job Queue with a large task. By awaiting a timeOut in an async method, that method effectively becomes the lowest-priority entity in the job queue. It seems weird screen transitions would be non-blocking since the UI is unavailable during this time, but I'm sure the react native developers had a reason... It also seems quite strange that the documentation recommends performing asynchronous work in this method when it will block without this queue relegation voodoo... Is there a better way to specify message/job queue priority on a Javascript Method beyond simple async? Or is awaiting empty timeOuts the only way to indicate lowest-priority processing.

Using expo sdk 34 which uses react-native 0.59

react-navigation 3.11

And React 16.8.3

EDIT: What a coincidence! Experimental support for Suspense for Data Fetching just announced by Dan 5 hours ago :) More here

2 Answers

Seems like your NetworkRequest.getData(payload) function is doing something expensive synchronously. Because JavaScript is single threaded, the next action doesn't happen until this function returns which can cause a lag.

By adding await timeOut(0), you no longer make it synchronous. It's now called in the next event loop which means any synchronous actions before that would finish already.

Why does the navigation between screens visually lag when doing async...

Maybe it can be laggy because you are on dev mode. Please try it on production.

Why does this visual delay disappear entirely when I await a brief timeout before doing the real async work?

I'm not sure what is NetworkRequest.getData doing, I can be something about it, but if it returns a promise, maybe you should try using await on it.

You are also forgetting to add async to your componentDidMount.

async componentDidMount(){
  await NetworkRequest.getData(payload);
}
Related