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` +
+
+ +
+ + +
+ `, + ({ get }) => { + get('input').type('hello') + get('input').blur() + get('span').should(haveText('hello')) + // Remove the input from the DOM via x-if — this triggers cleanup + get('button').click() + get('input').should('not.exist') + // Alpine should still be functional after cleanup + get('span').should(haveText('hello')) + } +) + +test('x-model.blur cleanup does not crash when element without form is removed', + html` +
+ + + +
+ `, + ({ get }) => { + get('input').type('world') + get('input').blur() + get('span').should(haveText('world')) + get('button').click() + get('input').should('not.exist') + get('span').should(haveText('world')) + } +) + +test('x-model.blur form submit still works after sibling input is removed', + html` +
+
+ + + + +
+ +
+ `, + ({ get }) => { + get('#name').type('Alice') + get('#extra').type('!') + get('#extra').blur() + // Remove the extra input — cleanup must not corrupt the form + get('#remove').click() + get('#extra').should('not.exist') + // Submit should still work with the remaining input + get('form').then(([form]) => form.requestSubmit()) + get('span').should(haveText('Alice!')) + } +) +