Skip to content

noLateEventControl (JavaScript)

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

Disallow calling preventDefault(), stopPropagation(), or stopImmediatePropagation() after the browser has finished handling the event.

When an event happens, the browser runs each event handler for it, one after another. preventDefault(), stopPropagation(), and stopImmediatePropagation() only work while the browser is still running these handlers. When a handler pauses, the browser moves on without waiting for it, so calling these methods afterwards does nothing: the browser still performs the default action, such as following a link, and other handlers still receive the event.

Code in a handler runs too late in these cases:

  • The code comes after an await or a for await loop in an async function.
  • 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.
  • The handler is a generator function (function*). Calling a generator function doesn’t run its body.

Call these methods directly in the handler, before the first await.

The rule checks calls on 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 call inside a nested function is reported, even when the nested function runs right away, like the callback of array.map(). Move the call to the handler itself to make it clear that it runs in time.
  • The rule doesn’t follow the order in which code runs. A call is reported when an await appears earlier in the handler, even if the await is in a branch that never runs together with the call.
  • When the event is passed to another function, calls inside that function are not checked.
element.addEventListener("click", async (event) => {
await save();
event.preventDefault();
});
code-block.js:3:5 lint/nursery/noLateEventControl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

⚠ event.preventDefault() is called after the event handler pauses.

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

ℹ The event handler pauses here.

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

ℹ When the handler pauses, the browser finishes handling the event without waiting for it, so this call has no effect.

ℹ Call event.preventDefault() before the event handler pauses.

ℹ 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.stopPropagation();
});
});
code-block.js:3:9 lint/nursery/noLateEventControl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

⚠ event.stopPropagation() is called inside a function nested in the event handler.

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

ℹ The nested function may run after the browser finishes handling the event, when this call has no effect.

ℹ Call event.stopPropagation() directly in the event handler, before any delayed work.

ℹ 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.onclick = function* (event) {
event.preventDefault();
};
code-block.js:2:5 lint/nursery/noLateEventControl ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

⚠ event.preventDefault() is called inside a generator function.

1 │ element.onclick = function* (event) {
> 2 │ event.preventDefault();
│ ^^^^^^^^^^^^^^^^^^^^^^
3 │ };
4 │

ℹ Calling a generator function doesn't run its body, so this call doesn't happen while the browser handles the event.

ℹ Use a regular function as the event handler.

ℹ 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) => {
event.preventDefault();
await save();
});
element.addEventListener("click", (event) => {
event.stopPropagation();
setTimeout(() => {
update();
});
});