tal3nce's avatar

Jetstream's Confirmation modal changes property type and throws ErrorException Array to string conversion

I have a fairly simple page (Livewire full page components) where I list information about a certain business app we're supporting. So it has the basic stuff such as name, description, support contact etc. and also a few downloadable files as guidelines. The issue is that on this page I also have a delete button (this is on admin panel end) to call a confirmation modal for deletion of this resource. And when I click on this button to toggle the $confirmationModal variable, my $guideline variable changes from an object to a simple array. So instead of an array of Models, I get an array of arrays in the $guidelines.

Livewire class:

<?php

namespace App\Http\Livewire\Pages\Admin;

use App\Models\Resource;
use App\Models\ResourceLink;
use Livewire\Component;

class BusinessApplication extends Component
{
    (...)
    public $guidelines;
    
    public $confirmationModal = false;

    public function mount(Resource $app)
    {
        (...)
        $this->guidelines = $app->links->where('type', '=', ResourceLink::DOWNLOADABLE)->all();
    }

 (...)

    public function render()
    {
        return view('livewire.pages.admin.business-application');
    }
}


And here is the blade file:

<div>
(...)

<x-button type="button" wire:click="$toggle('confirmationModal')" class="!px-1 !py-1 border !border-red-500 hover:bg-gray-100 hover:scale-110" title="Delete">
<x-icons.icon-trash class="w-4 fill-red-500" />
</x-button>


(...)

<div class="my-10 p-2 bg-gray-100 border-b border-gray-200 shadow-md">
            <div class="flex items-start justify-between">
                <x-jet-application-logo class="flex-shrink-0 w-12" />
                @if ($guidelines)
                <div class="grow mx-2">
                    @foreach ($guidelines as $guideline)
                    {{ var_dump($guideline) }}
                    <a href="{{ route('resources.download', [
                        'file' => $guideline
                    ]) }}" class="inline-flex items-center mr-1 mb-1 p-1 space-x-1 border rounded-md underline hover:bg-gray-200 hover:border-gray-400 text-sm">
                        <x-icons.icon-file-download class="!h-5 fill-red-800" />
                        <span>{{ $guideline->location }}</span>
                    </a>
                    @endforeach
                </div>
                @endif
                <div class="flex flex-col items-end flex-shrink-0 space-y-2 font-medium">
                    @if ($support)
 						(...)
                    @endif
    
                </div>
            </div>
        </div>


(...)

    <x-jet-confirmation-modal wire:model="confirmationModal">
        <x-slot name="title">Are you sure?</x-slot>
        <x-slot name="content">This action cannot be undone.</x-slot>
        <x-slot name="footer" class="space-x-5">
            <x-button type="button" wire:click="delete" class="bg-red-600 hover:bg-red-700 active:bg-red-700">
                Delete
            </x-button>
            <x-button type="button" wire:click="$toggle('confirmationModal')" class="bg-gray-400 hover:bg-gray-300">Cancel</x-button>
        </x-slot>
    </x-jet-confirmation-modal>

As soon as I click on the Delete button and modal starts loading, my single $guideline variable changes from something like this:

object(App\Models\ResourceLink)#1630 (30) { ["guarded":protected]=> array(0) { } ["connection":protected]=> string(5) "mysql" ["table":protected]=> string(14) "resource_links" ["primaryKey":protected]=> string(2) "id" ["keyType":protected]=> string(3) "int" ["incrementing"]=> bool(true) ["with":protected]=> array(0) { } ["withCount":protected]=> array(0) { } ["preventsLazyLoading"]=> bool(true) ["perPage":protected]=> int(15) ["exists"]=> bool(true) ["wasRecentlyCreated"]=> bool(false) ["escapeWhenCastingToString":protected]=> bool(false) ["attributes":protected]=> array(6) { ["id"]=> int(24) ["location"]=> string(29) "resources/changed unicorn.jpg" ["type"]=> int(1) ["resource_id"]=> int(20) ["created_at"]=> string(19) "2023-12-22 20:00:54" ["updated_at"]=> string(19) "2023-12-22 20:00:54" } ["original":protected]=> array(6) { ["id"]=> int(24) ["location"]=> string(29) "resources/changed unicorn.jpg" ["type"]=> int(1) ["resource_id"]=> int(20) ["created_at"]=> string(19) "2023-12-22 20:00:54" ["updated_at"]=> string(19) "2023-12-22 20:00:54" } ["changes":protected]=> array(0) { } ["casts":protected]=> array(0) { } ["classCastCache":protected]=> array(0) { } ["attributeCastCache":protected]=> array(0) { } ["dates":protected]=> array(0) { } ["dateFormat":protected]=> NULL ["appends":protected]=> array(0) { } ["dispatchesEvents":protected]=> array(0) { } ["observables":protected]=> array(0) { } ["relations":protected]=> array(0) { } ["touches":protected]=> array(0) { } ["timestamps"]=> bool(true) ["hidden":protected]=> array(0) { } ["visible":protected]=> array(0) { } ["fillable":protected]=> array(0) { } }

into something like this:

array(6) { ["id"]=> int(24) ["location"]=> string(29) "resources/changed unicorn.jpg" ["type"]=> int(1) ["resource_id"]=> int(20) ["created_at"]=> string(27) "2023-12-22T20:00:54.000000Z" ["updated_at"]=> string(27) "2023-12-22T20:00:54.000000Z" }

Just to note: this is not a huge issue, it works when I approach each $guideline as an array in the Blade file (e.g. $guideline['location'] instead of $guideline->location), but it's weird because this page is pretty much copy-paste of a page on user end (where there's no option to delete, hence no modal) and there it works normally when I render (each) $guiedline as a Model (object), not a simple array (like in the initial example of the Blade file above). Also, since we have a horrible tech stack, I'm still using Laravel 8 and Livewire 2, but wondering if anyone encountered anything similar to this, especially if its still present on newer versions of the frameworks.

0 likes
1 reply
LaryAI's avatar
LaryAI
AI
Best Answer
Level 58

It seems like the issue you're encountering is related to how Livewire serializes and deserializes properties when updating the component. When you toggle the $confirmationModal property, Livewire refreshes the component, and during this process, it serializes your public properties to the frontend and then deserializes them back into PHP when the request comes back.

The all() method on a collection returns an array, but it seems like Livewire is not preserving the original models within that array when it's being serialized and deserialized. Instead, it's converting them to arrays.

To ensure that your $guidelines property remains a collection of models, you can use the load() method to eager load the relationship and then access it as a collection. Here's how you can modify your mount method:

public function mount(Resource $app)
{
    // ...
    $this->guidelines = $app->links()->where('type', '=', ResourceLink::DOWNLOADABLE)->get();
    // ...
}

By using get() instead of all(), you're ensuring that $guidelines is a collection of ResourceLink models, which should be preserved when Livewire serializes and deserializes the data.

Additionally, make sure that your ResourceLink model has the necessary casts for its attributes if you're using any non-standard types that need to be cast when being serialized.

If you still encounter issues, you can also consider using Livewire's protected $casts property to explicitly cast the guidelines property to a collection of models. However, this feature is available from Livewire version 2.5.0 onwards, and since you mentioned you're using Livewire 2, you might need to update Livewire to a newer version that supports this feature.

Here's how you would define the cast in your Livewire component if you were able to update:

protected $casts = [
    'guidelines' => 'collection',
];

This tells Livewire to always treat the guidelines property as a collection, which should help maintain the correct type during the serialization process.

Remember to check the Livewire documentation for the version you are using, as the features and syntax may differ between versions.

1 like

Please or to participate in this conversation.