From c2165843404b7487191bdd43c59f344293710f41 Mon Sep 17 00:00:00 2001 From: James Barlow <332269+manwithacat@users.noreply.github.com> Date: Mon, 23 Mar 2026 20:56:13 +0000 Subject: [PATCH] fix: capture form reference before cleanup in x-model.blur 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: https://github.com/alpinejs/alpine/discussions/4738 Regression from: https://github.com/alpinejs/alpine/pull/4729 --- packages/alpinejs/src/directives/x-model.js | 7 +- .../integration/directives/x-model.spec.js | 71 +++++++++++++++++++ 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/packages/alpinejs/src/directives/x-model.js b/packages/alpinejs/src/directives/x-model.js index 92e078ba4..01a22cbd7 100644 --- a/packages/alpinejs/src/directives/x-model.js +++ b/packages/alpinejs/src/directives/x-model.js @@ -84,12 +84,13 @@ directive('model', (el, { modifiers, expression }, { effect, cleanup }) => { // submit handler runs. Register a pending update on the form // so it can be flushed before submit handlers execute. if (el.form) { + let form = el.form let syncCallback = () => syncValue({ target: el }) - if (!el.form._x_pendingModelUpdates) el.form._x_pendingModelUpdates = [] - el.form._x_pendingModelUpdates.push(syncCallback) + if (!form._x_pendingModelUpdates) form._x_pendingModelUpdates = [] + form._x_pendingModelUpdates.push(syncCallback) - cleanup(() => el.form._x_pendingModelUpdates.splice(el.form._x_pendingModelUpdates.indexOf(syncCallback), 1)) + cleanup(() => form._x_pendingModelUpdates.splice(form._x_pendingModelUpdates.indexOf(syncCallback), 1)) } } diff --git a/tests/cypress/integration/directives/x-model.spec.js b/tests/cypress/integration/directives/x-model.spec.js index fa956304e..8c173c158 100644 --- a/tests/cypress/integration/directives/x-model.spec.js +++ b/tests/cypress/integration/directives/x-model.spec.js @@ -660,3 +660,74 @@ test('x-model.blur syncs value before form submit handler runs', } ) +test('x-model.blur cleanup does not crash when element is removed from DOM', + html` +