livewire: Weird bug when using @entangle with an array property.

Description

I have a property contains an array of Trix fields. Whenever user click add button, it will add a Trix input into that array.

This is my trix field (from Surge source code)

<div
    class="rounded-md shadow-sm"
    x-data="{
        value: @entangle($attributes->wire('model')),
        isFocused() { return document.activeElement !== this.$refs.trix },
        setValue() { this.$refs.trix.editor.loadHTML(this.value) },
    }"
    x-init="setValue(); $watch('value', () => isFocused() && setValue())"
    x-on:trix-change="value = $event.target.value"
    {{ $attributes->whereDoesntStartWith('wire:model') }}
    wire:ignore
>
    <input id="x" type="hidden">
    <trix-editor x-ref="trix" input="x" class="form-textarea block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"></trix-editor>
</div>

How I use it: <x-input.trix wire:model.lazy="followUps.{{ $loop->index }}.body"/>

The issue is when I press remove, it throws this error:

Screen Shot 2020-12-17 at 6 51 12 PM

I noticed that if I use wire:model.lazy it will throw that error, but doesn’t with wire:model.defer. However, if I use wire:model.defer and the body is empty it will throw the same error unless I type something.

Context

  • Livewire version: 2.3.5
  • Laravel version: 8.19.0
  • Alpine version: 2.7.3
  • Browser: Chrome

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 3
  • Comments: 27 (21 by maintainers)

Most upvoted comments

I init the array in the mount function and it worked :

   public function mount()
    {
        foreach ($this->fields as $index => $field) {
 
            if($field->field->value === "TextFormattedLongSummary") {
                $this->TextFormattedLongSummary[$index] = "";
            }
        }
    }

The fix for this issue PR #2224 has been merged, and should be available in the next release 🙂

@phuclh I have pushed up a branch called keyChange, your key setup in master wasn’t working properly, so I made the changes like I would do, to get it working.

I have then also fixed up your trix editor, the watcher you had setup on it was causing issues where it was causing another change to fire when it was getting removed and setting the value to an empty string. But as it was deferred, you weren’t noticing that until another request was sent, which was why the second box kept popping back up when you added another one.

Let me know how it goes.

Hope this helps!

@phuclh good catch! Hmm I guess because now that the wire:key is set, Livewire isn’t touching it (which is desired) so you will need to change to something other than $loop->index. The best way I can imagine, is instead of using id as an element of the array, maybe instead trying to make the uniqid() the key for the for each element in your follow ups array like

public function addFollowUp()
{
    if ($this->canAddMoreFollowUp()) {
        $newFollowUp = array(uniqid() => [
            'days'     => 1,
            'template' => '',
            'subject'  => 'Re: ' . $this->subject,
            'body'     => ''
        ]);

        $this->followUps = array_merge(
            $newFollowUp,
            $this->followUps
        );
    }
}

In your blade, change your foreach loop to have the ID @foreach ($followUps as $id => $followUp)

Change your wire:key to wire:key="{{ $id }}"

Then replace all of your {{ $loop->index }}, inside the followUps foreach loop, with {{ $id }}.

@joshhanley Wow, thank you so much. You are so great! 👍👍👍👍.

However, there is a notice here:

$attributes->wire('model')->value }} doesn’t create a unique id in this case because I am using array_unshift to push a new item into the top of the array so the id is always followUps.0.body.

Screen_Shot_2021-01-13_at_7_55_20_PM

@phuclh ok I have pushed up some fixes to your repo, so please test and let me know how it goes 🙂 I’ll also list the issues and the fixes here incase anyone else is interested.

First issue

First issue I looked at was your follow up emails, which are added dynamically. When you added two of them, removed the second one and the modified the first, the second would re-appear. This is a morphdom issue and was caused by the wire:key not being unique enough.

To fix this, I added an id key to your array and generated a uniqid() each time a new one was added to the array, so there was a way to track them properly. This is recommended if the elements don’t have a unique ID say from a database as a loop index/ loop key isn’t unique to the “item/row”. And then updated the wire:key to use this (see first screenshot).

It is also best practice to wrap a div around a foreach loop to ensure only the stuff inside that div are the generated elements (see second screenshot, not sure why it’s highlighting the second close div tag but you get the idea).

First screenshot image

Second screenshot image

Second issue

The second issue was your initial email template, if you had added some follow up emails and removed them, then tried to change the main email template a couple times, it would also error.

This was caused by your Trix component having a fixed ID, causing multiple inputs on the page to have the same ID causing all sorts of issues.So I have made it unique to the input, by making the ID the Livewire property name you pass into the blade component (see third screenshot).

Third screenshot image

Hope this helps!

@joshhanley I just invited you to the repo, I also attach a video in Readme. Thank you!

@phuclh if you can put it into a github repo and share me the link, that would be better, so I can just clone it 🙂 if you’d prefer to keep the code private, just make a private repo and invite me to it.

@phuclh yeah there is a bug with how entangle handles arrays.

Have a look at this open PR which fixes the issue #2224.

Hope this helps!