Skip to content

noLateCurrentTargetAccess (JavaScript)

Language JavaScript (and super languages)
biome.json
{
"linter": {
"rules": {
"nursery": {
"noLateCurrentTargetAccess": "error"
}
}
}
}

Disallow reading event.currentTarget after the browser has finished handling the event.

event.currentTarget is the element that the event handler was added to. The browser only sets it while it runs the event handlers for the event, one after another. When a handler pauses, the browser moves on without waiting for it and resets currentTarget to null. Reading currentTarget afterwards gives null instead of the element.

Code in a handler reads currentTarget too late in these cases:

  • The code comes after an await, a yield, or a for await loop in the handler.
  • The code is in a function nested inside the handler, such as a setTimeout() or .then() callback. The nested function can run after the handler has finished.

Save event.currentTarget to a variable before the handler pauses, and use that variable instead.

The rule checks a function parameter whose name looks like an event, such as e, event, evt, or a camelCase name ending with Event like clickEvent. In TypeScript, it also checks parameters with an event type, such as MouseEvent or React’s SyntheticEvent, and ignores parameters with a type that isn’t an event, such as string.

The rule has these limitations:

  • Every currentTarget read inside a nested function is reported, even when the nested function runs right away, like the callback of array.map(). Save currentTarget to a variable in the handler to make it clear that it’s read in time.
  • The rule doesn’t follow the order in which code runs. A read is reported when an await appears earlier in the handler, even if the await is in a branch that never runs together with the read.
  • When the event is passed to another function, reads inside that function are not checked.
element.addEventListener("click", async (event) => {
await save();
event.currentTarget.disabled = false;
});
code-block.js:3:5 lint/nursery/noLateCurrentTargetAccess ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

⚠ event.currentTarget is read after the event handler pauses.

1 │ element.addEventListener("click", async (event) => {
2 │ await save();
> 3 │ event.currentTarget.disabled = false;
│ ^^^^^^^^^^^^^^^^^^^
4 │ });
5 │

ℹ The event handler pauses here.

1 │ element.addEventListener("click", async (event) => {
> 2 │ await save();
│ ^^^^^^^^^^^^
3 │ event.currentTarget.disabled = false;
4 │ });

ℹ When the handler pauses, the browser finishes handling the event and sets currentTarget to null.

ℹ Save event.currentTarget to a variable before the event handler pauses, and use that variable instead.

ℹ This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.

element.addEventListener("click", (event) => {
setTimeout(() => {
event.currentTarget.focus();
});
});
code-block.js:3:9 lint/nursery/noLateCurrentTargetAccess ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

⚠ event.currentTarget is read inside a function nested in the event handler.

1 │ element.addEventListener("click", (event) => {
2 │ setTimeout(() => {
> 3 │ event.currentTarget.focus();
│ ^^^^^^^^^^^^^^^^^^^
4 │ });
5 │ });

ℹ The nested function may run after the browser finishes handling the event, when currentTarget is null.

ℹ Save event.currentTarget to a variable in the event handler, and use that variable in the nested function.

ℹ This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.

element.addEventListener("click", async (event) => {
const button = event.currentTarget;
button.disabled = true;
await save();
button.disabled = false;
});
element.addEventListener("click", (event) => {
const { currentTarget } = event;
setTimeout(() => {
currentTarget.focus();
});
});