Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

warpig's avatar
Level 12

Show array of images

Im scratching my head here wondering why I can't show some images that belong to a post, it's a livewire component, this is the class component:

<?php

namespace App\Http\Livewire;

use App\Models\Faro;
use App\Models\Image;
use Livewire\Component;
use Livewire\WithPagination;

class FaroPostsTable extends Component
{   
    use WithPagination;

    public $search = '';
    public $perPage = 25;
    public $sortField = 'id';
    public $sortAsc = true;
    public $selected = [];
    public $path = 'faro_posts_img/';

    public Faro $post;
    public Image $image;

    public function deletePost()
    {
        Faro::destroy($this->selected);
    }
    
    public function render()
    {
        return view('livewire.faro-posts-table', [
            'posts' => Faro::search($this->search)
                ->orderBy($this->sortField, $this->sortAsc ? 'asc' : 'desc')
                ->simplePaginate($this->perPage)
        ])
            ->layout('components.master');
    }
}

And this is nested blade component inside the livewire view:

                        <x-modal>
                            <x-faro-posts-img-modal hash="faro-posts-img-modal" :post="$post">
                                @foreach($post->images as $image)
                                    <img
                                        src="{{ asset($image->name) }}"
                                        alt=""
                                        class="h-full"
                                    >
                                @endforeach
                            </x-faro-posts-img-modal>
                        </x-modal>

As you can see there is a component <x-modal> that nested inside has the actual modal, named <x-faro-posts-img-modal>, the funny part is that I can't get it to display the image just by calling a foreach, nor can I even use the $post inside the Blade component, this is the only way that I made NOT throw an error exception.

This is the full livewire view component (it's long)

@can('review_posts')
    <div class="px-4">
        <div class="w-full flex flex-col items-center justify-center md:flex-row pb-10 space-y-4 md:space-y-0">
            <div class="w-3/6 mx-1 w-full">
                <input wire:model.debounce.300ms="search" type="text" class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"placeholder="Buscar publicaciones...">
            </div>
            <div class="w-1/6 relative mx-1 w-full">
                <select wire:model="sortField" class="block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500" id="grid-state">
                    <option value="id">ID</option>
                    <option value="title">Título</option>
                    <option value="created_at">Fecha publicación</option>
                </select>
            </div>
            <div class="w-1/6 relative mx-1 w-full">
                <select wire:model="sortAsc" class="block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500" id="grid-state">
                    <option value="1">Ascendiente</option>
                    <option value="0">Descendiente</option>
                </select>
            </div>

            @can('delete_posts') 
                <div class="w-1/6 relative mx-1 w-full">
                    <button wire:click="deletePost" class="flex items-center justify-center block appearance-none w-full md:h-12 bg-red-500 text-white text-sm py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500 hover:bg-red-700">
                        Eliminar publicación
                    </button>
                </div>
            @endcan

        </div>

        @if($posts->isNotEmpty())

        <table class="table-auto w-full mb-6 px-6">
            <thead>
                <tr>
                    <th class="px-4 py-2"></th>
                    <th class="px-4 py-2">ID</th>
                    <th class="px-4 py-2">Título</th>
                    <th class="px-4 py-2">Cuerpo</th>
                    <th class="px-4 py-2">Imagenes</th>
                    <th class="px-4 py-2">Fecha</th>
                </tr>
            </thead>
            <tbody>

                @foreach($posts as $post)
                    <tr>
                        <td class="border px-4 py-2">
                            <input 
                                wire:model="selected" 
                                value="{{ $post->id }}" 
                                type="checkbox"
                            >
                        </td>

                        <td class="border px-4 py-2">
                            {{ $post->id }}
                        </td>

                        <td class="border px-4 py-2">
                            <div class="flex items-start">
                            @can('delete_posts') 
                                    <form method="POST" action="faro/{{$post->id}}">
                                        @csrf
                                        @method('DELETE')
                                            <button 
                                                class="danger-btn" 
                                                type="submit" 
                                                onclick="return confirm('¿Estas seguro de continuar?')"> 
                                                    <svg
                                                        class="w-4 cursor-pointer mr-4"
                                                        viewBox="0 0 20 20" 
                                                        xmlns="http://www.w3.org/2000/svg"
                                                    >
                                                        <g id="Page-1" stroke="none" stroke-width="1" fill="black" fill-rule="evenodd">
                                                            <g id="icon-shape">
                                                                <path d="M2,2 L18,2 L18,4 L2,4 L2,2 Z M8,0 L12,0 L14,2 L6,2 L8,0 Z M3,6 L17,6 L16,20 L4,20 L3,6 Z M8,8 L9,8 L9,18 L8,18 L8,8 Z M11,8 L12,8 L12,18 L11,18 L11,8 Z" id="Combined-Shape"></path>
                                
                                                            </g>
                                                        </g>
                                                    </svg>
                                            </button>
                                    </form>
                                @endcan

                                @can('edit_posts')
                                    <a href="faro/{{ $post->id }}/edit">
                                        <svg 
                                            class="w-4 cursor-pointer mr-4"
                                            viewBox="0 0 20 20"
                                            xmlns="http://www.w3.org/2000/svg"
                                        >
                                            <g id="Page-1" stroke="none" stroke-width="1" fill="black" fill-rule="evenodd">
                                                <g id="icon-shape">
                                                    <path d="M12.2928932,3.70710678 L0,16 L0,20 L4,20 L16.2928932,7.70710678 L12.2928932,3.70710678 Z M13.7071068,2.29289322 L16,0 L20,4 L17.7071068,6.29289322 L13.7071068,2.29289322 Z" id="Combined-Shape"></path>
                                                </g>
                                            </g>
                                        </svg>
                                    </a>
                                @endcan
                                <div class="flex items-center justify-center">
                                    <a href="#" class="hover:font-semibold hover:underline hover:text-green-500 cursor-pointer text-sm">
                                        {{ $post->title }}
                                    </a>
                                </div>
                            </div>
                        </td>

                        <td class="border px-4 py-2 text-sm">
                            {{ substr(strip_tags($post->body), 0, 100) }} {{ strlen(strip_tags($post->body)) > 75 ? "..." : "" }}
                        </td>

                        <td 
                            class="border px-4 py-2 flex items-center justify-center"
                        >
                            <button
                                onclick="$modals.show('faro-posts-img-modal')"
                                class="bg-blue-100 border-2 border-blue-500 text-sm rounded-md p-1 text-blue-500 hover:bg-blue-500 hover:border-none hover:text-white transition ease-in-out"
                            >
                                Mostrar
                            </button>
                        </td>

                        <x-modal>
                            <x-faro-posts-img-modal hash="faro-posts-img-modal" :post="$post">
                                @foreach($post->images as $image)
                                    <img
                                        src="{{ asset($image->name) }}"
                                        alt=""
                                        class="h-full"
                                    >
                                @endforeach
                            </x-faro-posts-img-modal>
                        </x-modal>

                        <td class="border px-4 py-2 text-sm">
                            {{ $post->created_at->diffForHumans() }}
                        </td>
                    </tr>
                @endforeach
            </tbody>
        </table>

        {!! $posts->links() !!}
        @else
            @can('create_posts')
                <p class="text-center flex items-center justify-center">Oops! No existen publicaciones, <a href="/faro/create" class="text-purple-500 hover:underline pl-1"> haz una publicación</a><span style='font-size:25px; padding-left: 5px;'>&#9749;</span></p>
            @endcan
        @endif

    </div>
@endcan

<script>
    window.$modals = {
        show(hash) {
            window.dispatchEvent(
                new CustomEvent('modal', { 
                    detail: hash
                })
            );
        }
    }
</script>

This is the main problem:

if a post doesn't have any images at all and one does, the one with images they are suddenly now gone as in they don't appear anymore in the browser (response to 304) so it looks like whatever the last has, it affects all posts. Please help! also, if one set of images were uploaded and then again if another post is created, the initial post sort of "inherits" the images from the last one.

Edit part 3: seems that this behaviour only occurs on this particular piece of the website since in another page I am showing individual posts using the id, (this is mainly an "admin dashboard"), whenever I view a single post via the show() I get different sets of images that do correspond to each post so that part is fine, should I target the id of each post on the datatable, then?

0 likes
15 replies
devingray_'s avatar

In Livewire, when you do a foreach you need to tell livewire how to itterate over it.

this is done with a wire:key= much like in vue :key=""

So an example will be

@foreach ($items as $item)
  <p wire:key="{{ $loop->index }}">{{ $item }}</p>
@endforeach

in your case


@foreach($post->images as $image)
      <img
         wire:key="{{ $image->id }} "
          src="{{ asset($image->name) }}"
          alt=""
          class="h-full"
       >
@endforeach
warpig's avatar
Level 12

Still getting the same set of images for both posts :-/ should I specify a public function mount() for retrieving the images?

public function mount() 
{
	return $this->post->images;
}

or via the model, but I have already tried both of those approaches Im not sure what else to do.

devingray_'s avatar

Could you try it with a more simple approach? so instead of all of the HTML you currently have replace it with a minimal version. Like so

<div>
  @foreach($posts as $post)
      <div wire:key="{{ $post->id }}">
         <h1> {{ $post->title }} </h1>
         <div>
             @foreach($post->images as $image)
              <img src="{{ $image->name}}" /> 
             @endforeach 
         </div>
      </div> 
   @endforeach  
</div>

So if that works, build from there :)

warpig's avatar
Level 12

Yeah @devingray_ actually as a matter of fact I do have a section inside a view that I do foreach and each time I get a different set of images, so in it's basic form it does work and I have it like this:

show.blade.php

<x-app>
    <div>
        <a href="#" class="flex font-bold text-green-900 text-2xl">
            Descarga el boletín aqui
        </a>  
            <div class="mt-4">
                @foreach ($post->images as $image)
                    <img 
                        src="{{ asset($image->name) }}" 
                        class="mb-4 border-2" 
                    />
                @endforeach
                <h1 class="font-bold text-lg mb-4">{{ $post->title }}</h1>
                <p class="mb-4">{{ $post->body }}</p>
            </div>
    </div>
</x-app>
warpig's avatar
Level 12

This:

<div>
  @foreach($posts as $post)
      <div wire:key="{{ $post->id }}">
         <h1> {{ $post->title }} </h1>
         <div>
             @foreach($post->images as $image)
              <img src="{{ $image->name}}" /> 
             @endforeach 
         </div>
      </div> 
   @endforeach  
</div>

on that same Livewire component view, does actually works @devingray_

warpig's avatar
Level 12

Yeah im gonna try and rebuild this with the wire:key now, thanks so much @devingray_ :-) although right now it doesn't work I still need to rebuild it and see where it breaks!!

1 like
devingray_'s avatar

Also, the wire:key should go on the first foreach, above I said add it to the <img> tag, but should actually go after @foreach($posts as $post) the root element of the foreach so add it to the <tr>

@foreach($posts as $post)
  <tr wire:key={{ $post->id }}>
   ....
 </tr>
@endforeach
1 like
warpig's avatar
Level 12

@devingray_ I noticed that also, but I think it doesn't work because on this datatable I am also deleting records and I need that same variable to delete records.. so it's being used twice, is that maybe where the conflict is?

                                <form method="POST" action="faro/{{$post->id}}">
                                    @csrf
                                    @method('DELETE')
                                        <button 
                                            class="danger-btn" 
                                            type="submit" 
                                            onclick="return confirm('¿Estas seguro de continuar?')"> 
                                                <svg
                                                    class="w-4 cursor-pointer mr-4"
                                                    viewBox="0 0 20 20" 
                                                    xmlns="http://www.w3.org/2000/svg"
                                                >
                                                    <g id="Page-1" stroke="none" stroke-width="1" fill="black" fill-rule="evenodd">
                                                        <g id="icon-shape">
                                                            <path d="M2,2 L18,2 L18,4 L2,4 L2,2 Z M8,0 L12,0 L14,2 L6,2 L8,0 Z M3,6 L17,6 L16,20 L4,20 L3,6 Z M8,8 L9,8 L9,18 L8,18 L8,8 Z M11,8 L12,8 L12,18 L11,18 L11,8 Z" id="Combined-Shape"
                                                            ></path>
                                                        </g>
                                                    </g>
                                                </svg>
                                        </button>
                                </form>
devingray_'s avatar

You mean here action="faro/{{$post->id}}? It won't make any difference there

warpig's avatar
Level 12

I think it's because the modal has other properties, that's when it starts to break, for example I tried to move the modal outside of the blade component so I inserted the whole HTML structure I had for that and pasted in the datatable, got an erro exception message pointing to $attributes. So it might as well be the variables from there that are NOT on the Livewire class component.

devingray_'s avatar

You know what might be an issue but I am not sure.

You are doing foreach and adding the modal inside the foreach so if there was 3 items it would look something like this

Item
   -- Form
   -- Modal
Item
   -- Form
   -- Modal
Item
   -- Form
   -- Modal

Then lower down, you have

<script>
    window.$modals = {
        show(hash) {
            window.dispatchEvent(
                new CustomEvent('modal', { 
                    detail: hash
                })
            );
        }
    }
</script>

How does this script know to trigger the correct modal?

devingray_'s avatar
Level 8

There is this

onclick="$modals.show('faro-posts-img-modal')"

and faro-posts-img-modal is the hash for all modals.

so you would need to do something like this

onclick="$modals.show('faro-posts-img-modal-{{ $post->id }}')"

and then

<x-modal>
    <x-faro-posts-img-modal hash="faro-posts-img-modal-{{ $post->id }}" :post="$post">
...
    </x-faro-posts-img-modal>
 </x-modal>
1 like
warpig's avatar
Level 12

YES!! :-) this ! hehe... so the way this modal works is by a CustomEvent in Javascript, as you saw it's named "modal", I will probably need to change some names to be able to reuse this to something more generic but the way that this work if I remember correctly is that it uses the property of hash and I assign it a variable to it, so everytime I want to open up the modal I need to set it as a prop in the <x-faro-posts-img-modal> element.

<div 
    x-data="{show: false, hash: '{{ $hash }}'}" <!-- this line is to set the default state and a hash property with an assgined variable is entered -->
    x-show="show"
    x-on:modal.window="
        show = ($event.detail === hash); <!-- this line is how that custom event gets fired, with that 'hash' name -->
    "
    @keydown.escape.window="show = false" <!-- if you hit escape key the modal will be false, so it will close -->
    @hashchange.window="
        show = (location.hash === '#{{$hash}}'); <!-- this line is where the hash variable is used and it will look for a hash, via location.hash i believe.. -->
    "
>

after setting that it's just a matter of assigning the hash name to it like I mentioned and setting the script that will look for that event with the given name.. I shouldn't be explaining this because I've yet so much to learn about Javascript, in fact im thinking about signing up for a course on Udacity about Javascript.. oh well that's me :-). Cannot thank you enough I've been trying to debug this the entire day haha! Thanks @devingray_

Please or to participate in this conversation.