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
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
this.selectedDate
is cleared out
- re-render is enqueued as a microtask
- 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.