2

In my UI, if I click on a delete button, I show an input field. Now I'd like to set it focused when input is rendered.

But it seems like rendering of templates takes few milli seconds if we want to add focus, in this scenario is it normal to add timeouts? And that may be understandable. This even works with 0 milli-seconds. But why exactly?

specific code:

setTimeout(() => {
  this.template.querySelector(".selected-date").focus();
}, 0);

Here is a minimal example: https://webcomponents.dev/edit/EYyPIKcglFS67JzV7pU9

1 Answer 1

4

You can't focus on an element that isn't yet visible. Adding SetTimeout() works (even with 0) because it makes the focus() call happen after the rendering of the input has occurred.


Understanding Event Loop, Call Stack, Event & Job Queue in Javascript is a good write up of the below. There's an Event Loop in javascript:

  • Execution Stack - executes one by one by your program, a stack of frames
  • Queues - a message queue, which is a list of messages to be processed (async).
  • Event Loop - runs continuously to check if there's no frames in stack and checks the queue to move oldest message to the stack for execution.

Within that queue, you have a distinction in:

  • task queue
  • microtask queue

Microtasks (Promises being the example) have priority over any tasks and run when the execution stack is empty. So an order like so:

  • Execution stack is executed until empty
    • run all microtasks until empty
      • run tasks (oldest first)

The difference is that execution of microtasks continues until the queue is empty—even if new ones are scheduled in the interim. In other words, microtasks can enqueue new microtasks and those new microtasks will execute before the next task begins to run, and before the end of the current event loop iteration.

If you look at setTimeout(), you'll see the following under delay

The time, in milliseconds that the timer should wait before the specified function or code is executed. If this parameter is omitted, a value of 0 is used, meaning execute "immediately", or more accurately, the next event cycle. Note that in either case, the actual delay may be longer than intended; see Reasons for delays longer than specified below.

The reason they mention that makes sense when looking at what we mentioned above (microtask/task queue). It's added to the task queue after the delay has passed.

In your situation, your setTimeout() adds the task to execute .focus() and that task can only occur when the microtasks queue is empty and all tasks ahead of it in the queue have occurred.

In terms of waiting for the input to change, LWC & reactivity covers how this is handled

After a component is connected and rendered, a change (or mutation) to the component’s state marks the component as dirty and enqueues a microtask to rerender the component.

So for your example code, the following would occur

  1. this.selectedDate is cleared out
  2. re-render is enqueued as a microtask
  3. Your setTimeout() runs as soon as the task can which would be after the microtask queue (and re-render) is settled

Without the setTimeout(), your .focus() call would happen in the execution stack immediately - before the render/microtask occurs.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .