Skip to content

Flush x-model.blur value before form submit handlers run#4729

Merged
calebporzio merged 1 commit intomainfrom
josh/submit-blur
Feb 2, 2026
Merged

Flush x-model.blur value before form submit handlers run#4729
calebporzio merged 1 commit intomainfrom
josh/submit-blur

Conversation

@joshhanley
Copy link
Copy Markdown
Collaborator

The Scenario

When using x-model.blur on an input inside a form, submitting the form by pressing Enter from that input does not sync the input's value before the submit handler runs. The handler sees stale (empty) data.

<div x-data="{ name: '' }">
    <form @submit.prevent="console.log('name:', name)">
        <input x-model.blur="name" type="text">
        <button type="submit">Submit</button>
    </form>
</div>

The Problem

The browser fires events in this order when Enter is pressed inside a form input:

  1. keydown (Enter) on the input
  2. submit on the form
  3. blur on the input (only after submit)

Since blur fires after submit, Alpine's x-model.blur listener never syncs the value before the @submit handler executes. The .change and .enter modifiers are not affected — the browser fires change and keydown before submit.

The Solution

When x-model.blur is used on an input inside a form, a pending model update callback is registered on the form element (el.form._x_pendingModelUpdates). The on() event utility then flushes any pending model updates before running submit handlers.

  • x-model.js: When .blur is present and the input is inside a form, registers a sync callback on the form element and cleans it up when the directive is removed
  • on.js: When setting up any submit event handler, wraps it to flush _x_pendingModelUpdates on the form before the handler executes

When using `x-model.blur` on an input inside a form, submitting via
Enter key does not sync the value before the submit handler executes.
The browser fires events in order: keydown → submit → blur, so the
blur listener never runs before the submit handler reads the value.

Register pending model update callbacks on the form element and flush
them before any submit handler runs via the `on()` utility.
joshhanley added a commit to livewire/livewire that referenced this pull request Jan 30, 2026
The proper fix for this belongs in Alpine's `x-model.blur` directive.
See alpinejs/alpine#4729.
@jeffchown
Copy link
Copy Markdown

Nice! Makes sense to address it Alpine. Thanks again, @joshhanley !

@calebporzio calebporzio merged commit a3b9829 into main Feb 2, 2026
2 checks passed
@calebporzio calebporzio deleted the josh/submit-blur branch February 2, 2026 22:36
manwithacat added a commit to manwithacat/alpine that referenced this pull request Mar 23, 2026
When an element with x-model.blur is removed from the DOM (e.g., via
x-if or Livewire morphing), the cleanup callback reads el.form which
returns null for detached elements. This causes a TypeError on
null._x_pendingModelUpdates.

Fix: capture `let form = el.form` at registration time so the cleanup
closure uses a stable reference that remains valid after detachment.

Adds three Cypress tests:
- x-model.blur input removed via x-if (core crash case)
- x-model.blur input without form removed via x-if
- form submit after sibling x-model.blur input removed

Ref: alpinejs#4738
Regression from: alpinejs#4729
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants