laravel-livewire-tables icon indicating copy to clipboard operation
laravel-livewire-tables copied to clipboard

[Bug]: The component class constructor doesn't seem to run when using ComponentColumn

Open nathan-io opened this issue 1 year ago • 54 comments

What happened?

We're trying to use a Blade component as a column:

            ComponentColumn::make('Fine Content', 'fine_weight_ozt')
                ->component('weight-with-conversions')
                ->attributes(fn ($value, $row, Column $column) => [
                    'weight' => new Weight($row->weighing_unit_type, $row->fine_weight),
                ]),

This does cause livewire-tables to attempt to render the component view, but it throws an "Undefined variable" exception because the public properties that are set in the constructor aren't present.

I don't believe the problem is in the component, because it works without issue when called in a view:

@php
     $weight = new App\Data\Weight(1, 1)
@endphp
<x-weight-with-conversions :weight="$weight" />

So perhaps the constructor is not firing?


Here's the component class and view:

...
class WeightWithConversions extends Component
{
    public Weight $weight;
    public string $conversions;

    /**
     * Create a new component instance.
     */
    public function __construct(Weight $weight)
    {
        $this->weight = $weight;

        $this->conversions =
            $weight->toString(WeighingUnitType::TroyOunce) . '<br/>' .
            $weight->toString(WeighingUnitType::Gram) . '<br>' .
            $weight->toString(WeighingUnitType::Pennyweight);
    }

    /**
     * Get the view / contents that represent the component.
     */
    public function render(): View|Closure|string
    {
        return view('components.weight-with-conversions');
    }
}

View:

<button data-popover data-tippy-content="{!! $conversions !!}" class="underline-dotted">
    {{ $weight->toString() }}
</button>

How to reproduce the bug

No response

Package Version

3.1.0

PHP Version

8.1.x

Laravel Version

10

Alpine Version

No response

Theme

None

Notes

No response

Error Message

No response

nathan-io avatar Nov 01 '23 17:11 nathan-io

Thanks for raising, I'll have a look in a bit, just so I can confirm:

class WeightWithConversions extends Component

Is that: Livewire\Component or Illuminate\View\Component

I'm assuming the latter?

lrljoe avatar Nov 01 '23 17:11 lrljoe

It's a Blade component.

<?php

namespace App\View\Components;

use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
use App\Data\Weight;
use App\Enums\Item\WeighingUnitType;

class WeightWithConversions extends Component
{
    public Weight $weight;
    public string $conversions;

    /**
     * Create a new component instance.
     */
    public function __construct(Weight $weight)
    {
        $this->weight = $weight;

        $this->conversions =
            $weight->toString(WeighingUnitType::TroyOunce) . '<br/>' .
            $weight->toString(WeighingUnitType::Gram) . '<br>' .
            $weight->toString(WeighingUnitType::Pennyweight);
    }

    /**
     * Get the view / contents that represent the component.
     */
    public function render(): View|Closure|string
    {
        return view('components.weight-with-conversions');
    }
}

nathan-io avatar Nov 01 '23 17:11 nathan-io

Perfect, thanks, I'll take a look shortly.

lrljoe avatar Nov 01 '23 17:11 lrljoe

Just having a look at why this isn't working for you, as I have a ComponentColumn working in my test environment. Will see if I can figure out why it works for me and not for you!

lrljoe avatar Nov 01 '23 21:11 lrljoe

Thanks for looking into this!

I see one potential issue (weighing_unit_type has a cast), I'll investigate and update this.

nathan-io avatar Nov 01 '23 22:11 nathan-io

I looked at it, still no luck. The Weight constructor throws an exception if supplied invalid arguments.

I put a dd() in the component constructor, but I don't get a dump when I view the page with the table. I move the dd() to the component view, and I do.

Can you show me your working test?

nathan-io avatar Nov 02 '23 00:11 nathan-io

I'll have to share it tomorrow, as I'm tuckered out now: See Here for why! That should all get merged in, possibly tomorrow as a new release. But then I will definitely share my working example!

Although glancing at it, I think mine may be an anonymous component, which would explain a lot as to why mine is working and yours isn't.

I'll pop the code open tomorrow to see if I can figure out what's going on there.

lrljoe avatar Nov 02 '23 00:11 lrljoe

Although glancing at it, I think mine may be an anonymous component, which would explain a lot as to why mine is working and yours isn't.

Yes I think this is the difference.

nathan-io avatar Nov 03 '23 01:11 nathan-io

Hi, wanted to see if you've had any chance to take a look at this. Thank you!

nathan-io avatar Nov 17 '23 20:11 nathan-io

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Dec 17 '23 22:12 stale[bot]

Don't close :)

nathan-io avatar Dec 17 '23 22:12 nathan-io

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jan 18 '24 00:01 stale[bot]

Is this something that can be looked at?

nathan-io avatar Jan 22 '24 17:01 nathan-io

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Feb 22 '24 06:02 stale[bot]

.

nathan-io avatar Feb 23 '24 17:02 nathan-io

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Mar 30 '24 18:03 stale[bot]

This is still an issue

nathan-io avatar Jun 20 '24 16:06 nathan-io

Apologies, stalebot gets a bit over-eager (I have no control over that!)

I'll do my best to take a look at this over the next week (so expect an update by the end of the month).

I have tinkered a little, and have got this resolved in my local environment (I've added a new Column type to avoid any conflicts with existing code).

If you want to give it a whirl, ping me on Discord and I'll let you know what to do to give it a test (would appreciate a real-world test tbh)

lrljoe avatar Jun 22 '24 22:06 lrljoe

Please do reach out if you want to test this, as otherwise I'll throw it into the next version, and it may not do what you want!

lrljoe avatar Jul 03 '24 22:07 lrljoe

Hello! I don't really use Discord, could you post some code here? I could temporarily modify my local vendor files and test it.

nathan-io avatar Jul 04 '24 13:07 nathan-io

To test this, all you need is a component which has a class constructor that sets some variable. Then reference that variable in the component view to make sure it's available.

nathan-io avatar Jul 04 '24 13:07 nathan-io

Okay, replicated, and have a fix for it, it'll come out as a new ViewComponentColumn type.

lrljoe avatar Jul 16 '24 04:07 lrljoe

I will need to write:

  • Docs (Completed)
  • Tests (In Progress)

Before this can be released. Estimate is by end of the month

Please do feel free to test in the meantime!

lrljoe avatar Jul 16 '24 05:07 lrljoe

There's a new ViewComponentColumn which fixes this issue. Let me know if you have any issues. Docs should be on the site shortly.

lrljoe avatar Jul 20 '24 00:07 lrljoe

Docs are released for ViewComponentColumn which should fix this! Please reply if issue persists.

lrljoe avatar Jul 24 '24 04:07 lrljoe

Thank you! I'll try to test this weekend and let you know how it works.

nathan-io avatar Jul 25 '24 14:07 nathan-io

I was able to try this out but am still having trouble.

Here's the latest thing I've tried:

            ComponentColumn::make('Fine Content', 'fine_weight_ozt')
                ->component('weight-with-conversions')
                ->attributes(fn ($value, $row, Column $column) => [
                    'weight' => new Weight($row->weighing_unit_type->value, $row->fine_weight),
                ])

I've also tried ViewComponentColumn with the same result. The docs say to use ComponentColumn, but the actual class name is ViewComponentColumn.

When I use this, I get an exception: https://gyazo.com/de0a72a769ca3b0bab45ec97b2e2f417

It doesn't seem like the exception itself is meaningful, as the underlying issue is that for whatever reason, the weight property we're passing into this component column is used as the value for the heading property in a completely different Blade component, which lives on the same view that we're calling the Livewire table from.

nathan-io avatar Aug 04 '24 00:08 nathan-io

@nathan-io - Can you share your weight-with-conversions component if possible?

I'm assuming that retail-catalog is the other component that seems to be interfered with?

Is the retail-catalog Component a child component of the same parent as the table? (i.e.

"VIew Things" has a Data Table Livewire Component and a Retail Catalog View Component

A broken DOM can also cause properties to be leaked between different components

lrljoe avatar Aug 04 '24 00:08 lrljoe

I found a dd() I had left in the component class. I haven't worked on this in a few months so I totally forgot about that.

Unfortunately that wasn't the issue, but it at least explains why it didn't matter if I used ViewComponentColumn or ComponentColumn (have to use the latter).

I'm now getting an Undefined variable $conversions exception in the component view weight-with-conversions.blade.php.

$conversions is a public property set in the component class WeightWithConversions, so it should be available there.

When I tried {!! $conversions ?? '' !!}, the exception changed to Undefined variable $weight.

$weight is another public property set in the component class.

The component works fine when we just call it like:

<x-weight-with-conversions :weight="$itemWeight"/>

So to test, you probably don't need App\Data\Weight or App\Enums\Item\WeighingUnitType. You can just set the properties to some arbitrary value, as I think the real issue is that the component class still isn't firing and making its properties available to the view.

Usage

package v3.3.4

            ComponentColumn::make('Fine Content', 'fine_weight_ozt')
                ->component('weight-with-conversions')
                ->attributes(fn ($value, $row, Column $column) => [
                    'weight' => new Weight($row->weighing_unit_type->value, $row->fine_weight),
                    // 'conversions' => '' // The constructor should set this for us but still got `Undefined variable $conversions` when including this as an experiment
                ])->sortable(
                    fn(Builder $query, string $direction) => $query->orderBy('fine_weight_ozt', $direction)
                ),

Component class

<?php

namespace App\View\Components;

use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
use App\Data\Weight;
use App\Enums\Item\WeighingUnitType;

class WeightWithConversions extends Component
{
    public Weight $weight;
    public string $conversions;

    /**
     * Create a new component instance.
     */
    public function __construct(Weight $weight)
    {
        $this->weight = $weight;

        $this->conversions =
            $weight->toString(WeighingUnitType::TroyOunce) . '<br/>' .
            $weight->toString(WeighingUnitType::Gram) . '<br>' .
            $weight->toString(WeighingUnitType::Pennyweight);
    }

    /**
     * Get the view / contents that represent the component.
     */
    public function render(): View|Closure|string
    {
        return view('components.weight-with-conversions');
    }
}

The component view

<button data-popover data-tippy-content="{!! $conversions !!}" class="underline-dotted">
    {{ $weight->toString() }}
</button>

nathan-io avatar Aug 04 '24 01:08 nathan-io

Hmmm, do all of your rows have both $row->weighing_unit_type->value and $row->fine_weight

Otherwise you may not be able to create a Weight, which may result in the error you're experiencing, not saying that's definitely the issue, but possible

lrljoe avatar Aug 04 '24 03:08 lrljoe

Unfortunately that's not the issue. weighing_unit_type and fine_weight aren't nullable, and all of the values for both columns are set and validated during seeding.

It really just seems like the component class just isn't being invoked, and thus it isn't making data available to the view.

nathan-io avatar Aug 04 '24 03:08 nathan-io

I think I may have just figured out why, just testing to see if it's the case!

lrljoe avatar Aug 04 '24 03:08 lrljoe

So, I've made a tweak, and think it should be fixed for the next version. To demonstrate what I'm doing to test:

My test column looks like this, just using notes_count as it's a value I already have in a test table.

            ViewComponentColumn::make('Weight', 'name')
            ->component('test-weight')
            ->attributes(fn ($value, $row, Column $column) => [
                'weight' => new Weight(($row->notes_count ?? 10)*rand(5,240))
            ]),

test-weight View Component

<?php

namespace App\View\Components;

class TestWeight extends \Illuminate\View\Component
{
    public ?\App\Weight $weight;

    public function __construct(\App\Weight $weight)
    {
        $this->weight = $weight;
    }

    /**
     * Get the view / contents that represent the component.
     */
    public function render(): \Illuminate\Contracts\View\View|Closure|string
    {
        return view('test-weight');
    }

}

test-weight.blade.php

<div>
    {{ $weight->getConversion() ?? 'Unknown'}}
</div>

Weight Class

<?php

namespace App;

class Weight
{
    public int $grams;
    public int $ounces;

    public function __construct(int $grams)
    {
        $this->grams = $grams;
        $this->setOunces();
    }

    public function setOunces()
    {
        $this->ounces = $this->grams / 28.3495;
    }

    public function getConversion(): string
    {
        return $this->grams."g, ".$this->ounces."oz";
    }
}

It seems to be working: image

lrljoe avatar Aug 04 '24 04:08 lrljoe

3.4.0 is out now

This fix will be in 3.4.1 which I'll release momentarily

lrljoe avatar Aug 04 '24 04:08 lrljoe