The short answer
You cannot have Livewire and a third-party library co-own the same element. wire:ignore is all-or-nothing for a subtree, so the fix is not a cleverer ignore — it is restructuring the template so the diffing boundary matches the ownership boundary. Put the JS-managed region inside a wire:ignore wrapper, and hoist the Livewire-updatable children outside that wrapper. For events flowing the other way (JS → Livewire), dispatch an event or set a property via $wire rather than letting the change ride a full render cycle.
Restructuring the template
Instead of nesting reactive content inside the widget's element, split it:
<div>
<div wire:ignore>
<!-- owned entirely by the JS library, e.g. a datepicker or map -->
<div id="map-canvas"></div>
</div>
<!-- Livewire owns this; it re-renders freely -->
<p>Selected: {{ $selection }}</p>
</div>
If the reactive child genuinely must live inside the JS-managed element, the usual escape hatch is to render it as a sibling and position it with CSS, or to let the JS library itself update that child from data you hand it — Livewire never touches it, so there is nothing to conflict over.
Re-initializing after morphs (Livewire v3)
When a morph does legitimately replace part of the widget's surroundings, re-init the library from an @script block or a morph hook:
Livewire.hook('morph.updating', ({ el, component }) => {
if (el.id === 'map-canvas') {
// skip or re-init as needed
}
});
Destroy the previous library instance before re-creating it — re-initializing blindly on every morph leaks event listeners and duplicates instances.
Syncing JS events into Livewire without a full re-render
From your library's callback, update server state directly:
picker.on('select', (date) => {
// v3: sets the property; if it isn't rendered, no visible re-render cost
$wire.$set('selectedDate', date);
// or emit an event other components can listen for
$wire.$dispatch('date-chosen', { date });
});
A property set this way only triggers DOM work if the template actually renders it, so state synchronization stays cheap. Reserve $wire.save()-style method calls for when you genuinely need server-side logic to run.
Version caveat and verification
This assumes Livewire v3 (@script, morph.updating, $dispatch). v2 uses different hooks and event APIs — check composer.lock for the livewire/livewire major version first. To confirm the diagnosis, add a temporary morph.updating hook that logs each patched element; if your JS-managed node appears there, the boundary split above is the fix. One detail that changes the recommendation: if the third-party library replaces its root element entirely (rather than mutating it), wire:ignore alone won't protect it, and you'll need the re-init hook approach as well.