zago27's avatar

wire:model.live data binding updates entire array instead of child object

I'm building a complex form with a basic header bound to a simple entity and several rows each related to another entity type.

I'm successfully decoupling these entities into an array, with each entity split into a few basic string and integer fields. The related Blade form is filled as expected on first render.

I'm now battling the editing part: these rows contain a few fields, where a hidden stores the underlying DB ID and an editable field keeps the related label as fetched from the DB. Each input is bound to form data through wire:model.live="details.{{ loop->index }}.dbId" and wire:model.live="details.{{ loop->index }}.dbLbl" respectively. Whenever I edit the label text field, however, I'm greeted with an update on the entire details array, which is replaced by whatever string I input. This, of course, causes mayhem and the page can no longer render the editor rows.

Here's a small excerpt from my source:

// Blade table builder

<div class="app-grid-editor">
    <div class="header">
        <div>{{ __('Article') }}</div>
        <div>{{ __('Material') }}</div>
        <div>{{ __('Quantity') }}</div>
    </div>

    @foreach ($orderDetails as $job)
        <div class="row">
            <div>
                <input type="hidden" wire:model="orderDetails.{{ $loop->index }}.artId">
                <input type="text" wire:model.live="orderDetails.{{ $loop->index }}.artLabel">
            </div>
            <div>
                <input type="hidden" wire:model="orderDetails.{{ $loop->index }}.matId">
                <input type="text" wire:model.live="orderDetails.{{ $loop->index }}.matLabel">
            </div>
            <div>
                <input type="number" wire:model.live="orderDetails.{{ $loop->index }}.qty">
            </div>
        </div>
    @endforeach
</div>
// Form component logic

public function mount(?Order $order = null)
{
    if ($order == null)
        return;

    $this->form->setOrder($order);

    foreach ($this->form->details as $job) {
        array_push($this->orderDetails, [
            'id' => $job->n_progr,
            'artId' => ($job->article ? $job->article->id : 0),
            'artLabel' => ($job->article ? $job->article->label : ""),
            'matId' => ($job->material ? $job->material->id : 0),
            'matLabel' => ($job->material ? $job->material->label : ""),
            'qty' => $job->qty,
        ]);
    }
}

public function updated($name, $value)
{
    \Debugbar::debug("updated -> " . $name . " " . $value);
    \Debugbar::debug($this->orderDetails);
    // => Here orderDetails has already turned into a string. $name matches this, as it has lost anything after the first dot

    if (!is_string($name) || !is_string($value))
        return;

    if (strlen($value) < 3)
        return;

    if ($name === "orderDetails") {
        // TODO: Prepare autocomplete data
    }
}

#[On('newOrderRow')]
public function newOrderRow()
{
    array_push($this->orderDetails, [
        'id' => 0,
        'artId' => 0,
        'artLabel' => "",
        'matId' => 0,
        'matLabel' => "",
        'qty' => 0,
    ]);
}

What am I doing wrong here? I have done something similar with a straight string property (omitted in the example), and it has worked fine...

Thanks!

0 likes
6 replies
Snapey's avatar

look in the browser html source and check your wire:model are a string in the expected format

zago27's avatar

They are indeed correct, like orderDetails.0.artId, orderDetails.1.artId and so on...

Snapey's avatar

use debugbar Livewire section to see what values are being updated as you type into the inputs

Personally I would use the record id as the index and not have a hidden field

Also make sure you use the record id as wire:key

zago27's avatar

Yeah, it's already in the excerpt above. It's printing orderDetails and then it's dumped as a string (see comment).

I can try adding wire:key for each row, that's a good point.

The hidden field is my way to decouple the text box from the bare entity info, so I can use it as an auto complete source. I'll simply edit the label, get a result box and when clicking on a result, I'll fetch the new ID and the full label.

Snapey's avatar

Yeah, it's already in the excerpt above

Not what I meant at all.

Debugbar has a Livewire tab. In this you see the public properties of the component.

You should see orderDetails.index updating as you type

zago27's avatar

Found my mistake, the entire table div was bound to the orderDetails property, so it was overriding the underlying row bindings... Whoops!

Please or to participate in this conversation.