Skip to content

Fix x-model sync for grouped dynamic options - #4921

Open
dmp990 wants to merge 1 commit into
alpinejs:mainfrom
dmp990:fix-x-model-optgroup-sync
Open

dmp990 wants to merge 1 commit into
alpinejs:mainfrom
dmp990:fix-x-model-optgroup-sync

Conversation

@dmp990

@dmp990 dmp990 commented Sep 27, 2026 •

Copy link
Copy Markdown

This PR fixes x-model synchronization for select options that are rendered dynamically inside an optgroup. Previously, the select-specific MutationObserver watched only direct children of the select element. When x-for inserted option elements beneath an optgroup, the mutation target was the optgroup, so x-model did not reapply the model value and the browser-selected option could disagree with Alpine state.

This can be reproduced with a select whose model is initialized to Orange and whose Red, Orange, and Yellow options are rendered by an x-for template inside an optgroup. On main, the model continues to report Orange while the select displays Red. The initial x-model effect runs before x-for creates the options, and the later nested mutation does not trigger a resynchronization. The same problem occurs when a matching option is inserted later while the model value remains unchanged.

This PR fixes the problem by enabling subtree observation on the existing select child-list observer. Additions and removals beneath optgroup elements now trigger the same x-model synchronization already used for options rendered directly beneath a select.

This PR also updates the x-model documentation to explain that dynamically added and removed options remain synchronized when nested inside an optgroup. The example now demonstrates grouped options rendered with x-for and an initial model value that selects a non-first option.

This PR adds Cypress coverage for both affected paths: selecting an initial model value after x-for renders options inside an optgroup, and resynchronizing when x-for later inserts an option matching an unchanged model value. The focused x-model suite passes with 42 tests and no failures using npx cypress run --spec ./tests/cypress/integration/directives/x-model.spec.js --browser electron.

@dmp990

dmp990 commented Sep 27, 2026

Copy link
Copy Markdown
Author

How to reproduce

On the current main branch, render dynamic options with x-for inside an <optgroup>:

<div x-data="{ selected: 'Orange' }">
    <select x-model="selected">
        <optgroup label="Colors">
            <template x-for="color in ['Red', 'Orange', 'Yellow']">
                <option :value="color" x-text="color"></option>
            </template>
        </optgroup>
    </select>

    <span x-text="selected"></span>
</div>

When the component initialises, the span reports Orange, but the select displays Red. The model and the rendered selection are therefore out of sync.

The expected behaviour is for the select to display Orange. The initial x-model update runs before x-for creates the options, and the subsequent mutation occurs inside the <optgroup>, where the existing direct-child observer cannot detect it.

This branch has not been deployed

No deployments
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.

1 participant