Nested components in Livewire with AlpineJS and @entangle directive

I’m currently developping a project with Livewire and AlpineJS.

I have to use a div with contenteditable set to “true” to access and modify my data model.

What I want

I would like to share the state between Livewire And Alpine using @entangle.

My code

My code is in two parts.
The first-part rely on a full-Page component where I simply register an array of $fruits.
The second, is a component containing a “form” allowing me to access and modify the data through @entangle and a div with contenteditable set to true.

<?php

namespace AppHttpLivewire;

use LivewireComponent;

class ListFruit extends Component
{

    protected $listeners = ['addFruit'];

    public array $fruits = [];

    public function mount()
    {
        $this->addFruit();
    }

    public function addFruit()
    {
        $this->fruits[] = $this->makeBlankFruit();
    }



    public function makeBlankFruit(): array
    {
        return [
            'type' => '',
            'color' => ''
        ];
    }

      public function render()
    {
        return view('livewire.list-fruit');
    }
}

The views/livewire/list-fruit.blade.php file

<div  class="w-full container mx-auto">

    <h3 class="text-2xl font-sembibold">
          Fruits
    </h3>

    <div id="fruits">
       @foreach($fruits as $fruitIndex=> $fruit)

         <livewire:fruit :fruitIndex="$fruitIndex" :fruit="fruit" :wire:key="$fruitIndex" >

       @endforeach
    </div>

</div>

Fruit Component

<?php

namespace AppHttpLivewire;

use LivewireComponent;

class Fruit extends Component
{

    public array $fruit = [];

    public string $fruitIndex;

    public function render()
    {
        return view('livewire.fruit');
    }
}

views/livewire/fruit.blade.php file
<div x-data="{type: @entangle('fruit.type'), color: @entangle('fruit.color')}"
    x-init="console.log(type)"
>
    <div>
        <div x-on:blur="type = $event.target.innerHTML" contenteditable="true">{{ $fruit['type'] }}</div>
        <div x-on:blur="color = $event.target.innerHTML" contenteditable="true">{{ $fruit['color'] }}</div>
    </div>

    <div>
        $type @ Livewire: {{ $fruit['type'] }}
        $color @ Livewire: {{ $fruit['color']  }}
    </div>
    <div>
        type @ AlpineJS: <span x-text="type"></span>
        color @ AlpineJS: <span x-text="color"></span>
    </div>
</div>

If I had only one array with some properties in my main component, let’s say :
 public array $fruit = [
  'type' => '',
  'color' => '',
 ];

I would be able to access them with my fruit.blade.php

I also tried to do something like this.

{type: @entangle('fruits.'. $fruitIndex.'.color'), color: @entangle('fruits.'. $fruitIndex.'.type')}

What I obtained

In my AlpineJs component I read an [object Object] proxy and when modifying my fruit.color or fruit.type property the livewire part isn’t updated.

I don’t know why.
In my final attempt, I tried to separate my fruits into multiple sub-components allowing me to work on a single array.

I’m a looking at a dead end, so thank you in advance for your help.
Tanuki

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

I think you should simplify your example. Although your real-world code is likely to be more complex than your (no doubt contrived) fruits example, it seems to me unnecessary to nest Livewire components here. It’s far simpler to keep one PHP/Livewire class and one Blade file. Then instead of using Blade to loop through your data with @foreach, use Alpine’s x-for. Contrary to current trends, I don’t always believe that it’s desirable to keep file-sizes small, if the cost is to nest layer upon layer of (increasingly difficult to debug) blade-templates (or Livewire components).

Anyway, that aside, let’s go back to the principle that you’re trying to establish here, which is how one entangles a PHP/Livewire array with a Javascript/Alpine variable.

The answer is surprisingly simple: you can entangle an arbitrarily complicated PHP data-structure (aka array) to a single JavaScript variable. As you would expect if you had built the framework yourself, the PHP array will simply be mapped to a JSON object.

First, set up your complicated back-end data-structure:

<?php

namespace AppBusinessTbd;

use LivewireComponent;

class StartLw extends Component
{
    public array $fruits = [];

    public function mount() {

        $fruit_template = ['apple', 'banana', 'garlic'];
        $fruit_colours  = ['green', 'yellow', 'pink'];
        $health_benefit = [
            'keeps doctor away',
            'the bend keeps you flexible',
            'None.  It's not really a fruit, though there is that vampire thing'
        ];

        foreach ($fruit_template as $id => $fruit) {
            $this->fruits[$fruit] = [
                'colour'    => $fruit_colours[$id],
                'benefit'   => $health_benefit[$id],
                'bought_by' => array_slice(['Jim', 'John', 'Sue'], 0, $id+1),
            ];
        }
    }

    public function render()
    {
        return view('my.livewire.template');
    }
}

Then the following Blade template will tell you all you need to know about the contents of the JavaScript/JSON object (which was originally created in PHP).

<div class="container-fluid">
    <div class="row" x-data="{ fruits: @entangle('fruits') }">
        <div class="col-11" id="main">

            <p>Type of fruit: <span x-text="typeof fruits"></span></p>

            <template x-for="(fruit_info, fruit) in fruits" :key="fruit">
                <code>
                    Fruit type: <span x-text="fruit"></span><br />
                    Colour: <span x-text="fruit_info.colour"></span><br />
                    Health Superstitions: <span x-text="fruit_info.benefit"></span><br />
                    Bought by:
                    <template x-for="(pers, id) in fruit_info.bought_by" :key="id">
                        <span>
                            <span x-text="id"></span> => <span x-text="pers"></span>,&nbsp;
                        </span>
                    </template>
                    <br />
                    <br />
                </code>
            </template>

        </div>
    </div>
</div>

As you see, if you build your Blade templates with enough flexibility, they can become agnostic to the underlying data-structures, which (in a modular application) means that they can be used over and over again.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x