Fix race condition causing data loss in wire:model.live updates
Under certain timing conditions (slow network, long-running pages, parallel requests), wire:model.live updates could cause data loss by replacing entire nested objects instead of updating individual properties.
The MessageBus buffers messages for 5ms before sending them (js/request/messageBus.js:81-85) to allow lifecycle hooks and interceptors to attach. While necessary, this creates a timing window where component state can change. Since getUpdates() is called at send time (js/request/index.js:175), the canonical state may have already been modified by other actions during the buffer. Combined with slow networks or parallel wire:model.live requests (allowed per js/request/interactions.js:68-74), this small window is enough for canonical and ephemeral to fall out of sync, triggering the race condition.
In real-life, this can cause the following component property (Filament):
public array $mountedActions = [
['data' => ['source' => 'incoming_call'], 'name' => 'foo']
];
With wire:model.live="mountedActions.0.data.source", changing the value would sometimes:
- Expected: Update only mountedActions.0.data.source
- Actual: Replace entire mountedActions[0], losing the name property
Correct:
Replaces the whole mountedActions.0 with this payload:
Root cause
A race condition in the state synchronization system:
- The diff() function compares canonical (server state) vs ephemeral (client state)
- When these states have type mismatches (e.g., empty array vs object), diff() returns the entire nested object instead of flat paths
- This happens when:
- Network requests are slow (canonical/ephemeral diverge during request)
- Multiple wire:model.live requests run in parallel
- Component state changes between capturing the diff and sending the request
- Pages have been open for extended periods
Code flow
// js/utils.js:119-122 - The problematic behavior
if (typeof left !== typeof right || ...) {
diffs[path] = right; // ← Returns entire nested object!
return diffs
}
When the server receives a nested object, it replaces the entire property (as designed), causing sibling properties to be lost.
Solution
Added a safety net in Component.getUpdates() that:
- Detects when diff() returned nested objects (indicates the race condition occurred)
- Automatically flattens those objects into proper dot-notated paths
- Preserves all properties and prevents data loss
The fix is safe, as the server-side code (HandleComponents.php) is explicitly designed to handle dot-notated paths only:
public function updateProperty($component, $path, $value, $context)
{
$segments = explode('.', $path); // ← Expects dots
// ...
}
Tests
The reproduction of this was very sporadic and I wasn't able to even consistently reproduce it in the browser unfortunately.