In Livewire 4, when a user edits the quantity of one row in a public $items array, which update hooks fire and with what arguments?
answer
- the path is dotted: items.2.quantity
- updated() gets the full path
- updatedItems($value, $key)
- key is everything after the first dot
- v4: whole-array replace fires once
basics
~10 sFor an update to items.2.quantity, Livewire 4 calls updating('items.2.quantity', $value) and updatingItems($value, '2.quantity') before the write, then updated('items.2.quantity', $value) and updatedItems($value, '2.quantity') after all updates are written.
solid answer
~40 sArray properties are updated by **dotted path**, such as `items.2.quantity`. The generic `updating($path, $value)` and `updated($path, $value)` receive that full path. The property-level hooks `updatingItems($value, $key)` and `updatedItems($value, $key)` receive the new leaf value and the **key after the first dot**, here `'2.quantity'`; when the whole array is replaced, `$key` is `null` and `$value` is the full array. Livewire also calls a nested-name hook built from the path with dots turned into underscores and StudlyCased, whose `$key` is the segment after the **last** dot. In Livewire 4, replacing a whole array from the browser sends one consolidated update, so the hooks fire once with the new array instead of once per changed index as in v3.
code
php · 18 lines<?php
use Livewire\Component;
new class extends Component {
public array $items = [
['sku' => 'A-1', 'quantity' => 1, 'unitPrice' => 900],
['sku' => 'B-7', 'quantity' => 2, 'unitPrice' => 450],
];
public int $total = 1800;
// items.1.quantity -> $key === '1.quantity'
public function updatedItems($value, ?string $key): void
{
$this->total = collect($this->items)
->sum(fn ($line) => (int) $line['quantity'] * $line['unitPrice']);
}
};go deeper
Recall that array changes arrive as dotted paths and that updatedItems() receives the value and the key after the first dot.
Explain the argument shapes of the generic and property-level hooks, the null key, and that updated hooks run after all writes.
Treat the path as untrusted input and keep per-row recomputation targeted, since live inputs send many updates.
Decide when a large editable table should stop living in one array property and move to child components or a form object.
## How Livewire addresses nested data A **public array property** such as `$items` on an order form can hold a list of lines, each with `sku`, `quantity` and `unitPrice`. The browser does not resend the whole array when one field changes; it sends an update for a **dotted path**, for example `items.2.quantity`. Livewire calls the `updating*` hooks, walks that path and writes the leaf value, and later calls the `updated*` hooks. Knowing what each hook receives is what lets one method recompute the right line. ## Which hooks fire for items.2.quantity | Hook | Called with | |---|---| | `updating($property, $value)` | `'items.2.quantity'`, new value | | `updatingItems($value, $key)` | new value, `'2.quantity'` | | `updated($property, $value)` | `'items.2.quantity'`, new value | | `updatedItems($value, $key)` | new value, `'2.quantity'` | So the property-level hook's `$key` is **everything after the first dot**. A typical order-form handler splits it: ```php public function updatedItems($value, ?string $key): void { if ($key === null) { $this->recalculateAll(); return; } [$row, $field] = explode('.', $key, 2) + [1 => null]; if ($field === 'quantity') { $this->recalculateLine((int) $row); } } ``` ## Nested-name hooks Livewire also tries a method built from the **whole path**: the dots become underscores and the result is StudlyCased, so a path `bar.baz` looks for `updatedBarBaz($value, $key)`. For that hook `$key` is the segment after the **last** dot. It is a niche feature; for list data with numeric indexes the property-level hook with its `$key` argument is the readable choice. ## When $key is null If the whole array is written at once, there is no sub-path, so the property-level hooks receive the complete new array as `$value` and `null` as `$key`. A handler must therefore treat `null` as "everything may have changed". ## What changed in Livewire 4 In **Livewire 3**, replacing a whole array from JavaScript (for example setting `$wire.items` to a new list) produced a series of granular updates, one per changed index plus special removal markers, so `updatedItems()` could fire several times for one user action. **Livewire 4** sends one consolidated update instead, so the hooks fire **once** with the full array. Editing a single row still sends a single-path update, so the per-row behaviour above is unchanged. Code that counted on per-index hook calls when a whole array is swapped needs to handle the `null`-key case. ## Timing inside the request - Each `updating*` hook fires as its own value is written, while the property still holds the old data. - All `updated*` hooks fire after **every** update in the request has been written, so a handler can read sibling fields that changed in the same request. - The action (if any) runs after that, then the view renders. ## Pitfalls interviewers look for 1. Assuming `$key` is the row index alone; it is the whole remaining path, `'2.quantity'`. 2. Forgetting the `null` key when the array is replaced wholesale. 3. Trusting the index: a user can send any path, so re-check that the row exists before using it. 4. Writing heavy per-row work in `updated()` without filtering on the path, which then runs for every property on the component. ## A readable pattern for list data For an order form with many lines, a clean structure is: 1. one `updatedItems($value, $key)` handler that splits `$key` into row and field; 2. a guard that the row exists and the field is one the user may edit; 3. a single `recalculateLine($row)` or `recalculateAll()` method that both the hook and any action call. This keeps the argument parsing in one place and makes the `null`-key case explicit. When the list grows large or each line gains its own behaviour, moving each line into its own child component gives every line its own simple, non-array update hooks; that decision belongs to how the component is structured rather than to the hooks themselves.
- Why can an updatedItems() handler safely read another field that changed in the same request?Livewire writes every incoming update first and calls the `updated*` hooks only after all of them are applied. So a request that changes `items.0.quantity` and `discountCode` together lets the quantity hook see the new discount code.
- Does validating inside updatedItems() protect the order total from a forged row index?Only if the handler checks it. The path comes from the client, so a request can target `items.99.quantity`. Guard the index and the value, or lock data the client must not change.
saying these in an interview costs you the question
- Says the $key argument is only the numeric row index.
- Expects updatedItems() to receive the full dotted path as its first argument.
- Forgets that $key is null when the whole array is replaced.
- Claims Livewire 4 still fires the hooks once per index on a whole-array replace.
- Thinks updated hooks run before the other updates of the same request are written.