Skip to content

[LiveComponent] Rebuild Component on reconnect when props changed in between#3537

Open
Amoifr wants to merge 1 commit into
symfony:3.xfrom
Amoifr:fix/live-component-stale-valuestore-on-reconnect
Open

[LiveComponent] Rebuild Component on reconnect when props changed in between#3537
Amoifr wants to merge 1 commit into
symfony:3.xfrom
Amoifr:fix/live-component-stale-valuestore-on-reconnect

Conversation

@Amoifr

@Amoifr Amoifr commented May 12, 2026

Copy link
Copy Markdown
Contributor
Q A
Bug fix? yes
New feature? no
Deprecations? no
Documentation? no
Issues Fix #3424
License MIT

Summary

When a parent live component action removes and re-adds a child live component on the same DOM element, Stimulus keeps the controller instance alive — it only calls disconnect() then connect() again, not initialize(). Because createComponent() only runs in initialize(), the Component (and its ValueStore) keep the stale props from before the parent action, even though the server-rendered HTML now exposes a fresh set.

The next user input then throws Uncaught Error: Invalid model name "xyz.content" — the model name has changed, but ValueStore.has() still looks up against the old props.

Fix

In connect(), compare the current propsValue (which Stimulus has refreshed from the live data-live-props-value attribute) against component.valueStore.getOriginalProps(). When they differ, rebuild the Component.

  • First connect() after initialize(): props match by construction → no rebuild.
  • Plain disconnect/reconnect without any prop change: props match → no rebuild.
  • Parent morph that swaps props on the same element: props diverge → rebuild.

The diagnosis and proposed fix are from @Pechynho in #3424 (confirmed working by another reporter in the thread).

Test plan

  • New unit test rebuilds the Component on reconnect when props changed in between exercises the bug scenario by calling controller.disconnect() / mutating propsValue / controller.connect(), and asserts the Component instance has been swapped and the new ValueStore reflects the fresh props.
  • New unit test keeps the existing Component on reconnect when props are unchanged asserts that a no-op reconnect cycle does not recreate the Component.
  • Full LiveComponent controller test suite still green (109 tests).
  • oxlint + oxfmt --check clean.

Fixes #3424

@Kocal

Kocal commented May 14, 2026

Copy link
Copy Markdown
Member

For 3.x, 2.x is not maintained anymore, and please follow the PR template (it's not the first time 😬)

…between

When a parent action removes and re-adds a child live component that lives
on the same DOM element, Stimulus reuses the controller instance — it only
calls disconnect() then connect() again, not initialize(). createComponent()
is therefore never re-run, so the Component keeps a stale ValueStore that no
longer matches the freshly rendered HTML, and Component.set() throws
"Invalid model name" the next time the user types into a field.

Detect the divergence in connect() by comparing the current propsValue with
the props the Component was originally built with, and rebuild it when they
differ. Behavior is unchanged on the first connect() (props match by
construction) and on plain reconnects without any prop change.

Fixes symfony#3424
@Amoifr Amoifr force-pushed the fix/live-component-stale-valuestore-on-reconnect branch from b1b1e48 to af195b6 Compare May 15, 2026 10:51
@Amoifr Amoifr requested a review from Kocal as a code owner May 15, 2026 10:51
@github-actions

Copy link
Copy Markdown
Contributor

📊 Packages dist files size difference

Thanks for the PR! Here is the difference in size of the packages dist files between the base branch and the PR.
Please review the changes and make sure they are expected.

FileBefore (Size / Gzip)After (Size / Gzip)
LazyImage
controller.d.ts 395 B / 257 B Removed
controller.js 904 B / 457 B Removed
Map
abstract_map_controller.d.ts 7.6 kB / 1.49 kB 7.29 kB-4% 📉 / 1.46 kB-2% 📉
abstract_map_controller.js 4.92 kB / 1.4 kB 4.65 kB-5% 📉 / 1.26 kB-10% 📉
Map (Bridge Google)
map_controller.d.ts 10.56 kB / 1.92 kB 10.26 kB-3% 📉 / 1.9 kB-1% 📉
map_controller.js 12.9 kB / 3.19 kB 11.25 kB-13% 📉 / 2.84 kB-11% 📉
Map (Bridge Leaflet)
map_controller.d.ts 9.92 kB / 1.84 kB 9.61 kB-3% 📉 / 1.81 kB-2% 📉
map_controller.js 12.45 kB / 3.36 kB 11.38 kB-9% 📉 / 3.17 kB-6% 📉
Svelte
components.d.ts 200 B / 150 B Removed
components.js 46 B / 69 B Removed
loader.d.ts 435 B / 217 B Removed
loader.js 553 B / 313 B Removed
register_controller.d.ts 384 B / 235 B Removed
register_controller.js 531 B / 303 B Removed
render_controller.d.ts 629 B / 353 B Removed
render_controller.js 1.05 kB / 493 B Removed
Swup
controller.d.ts 1012 B / 360 B Removed
controller.js 1.71 kB / 653 B Removed
TogglePassword
controller.d.ts 896 B / 355 B Removed
controller.js 2.64 kB / 1.07 kB Removed
style.min.css 312 B / 218 B Removed
Turbo
mercure_stream_source_element.d.ts Added 11 B / 66 B
mercure_stream_source_element.js Added 1.06 kB / 484 B
turbo_stream_controller.js 1.11 kB / 545 B 1.47 kB+32% 📈 / 721 B+32% 📈
Typed
controller.d.ts 1.9 kB / 501 B Removed
controller.js 1.8 kB / 638 B Removed

@Amoifr Amoifr changed the base branch from 2.x to 3.x May 15, 2026 10:56
@Amoifr

Amoifr commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

Sorry for the slip @Kocal — rebased onto 3.x and prepended the Q/A table to the body. I've also saved this as a checklist note on my side so the next PR opens with both right from the start. 🙏

@Amoifr

Amoifr commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

@Kocal in my defense — I catch on quick, you just have to explain things to me at length 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[UX Live Component] Bug: Stale ValueStore after component reconnect (disconnect → connect without initialize)

3 participants