skip to Main Content

I am trying to recreate a reusable blade component that will allow me to view and upload images for all my forms. The value in the livewire component could be a string of a previously uploaded image or a newly uploaded image.
What I need it the component to show me what image I have already set and the image Im am changing it to.

I have my livewire component that has a form with an image variable

class CreateStall extends Component
{
    use WithFileUploads;
    public editStallForm $form;

    public function render()
    {
        return <<<'HTML'
        <div> 
           <x-inputs.image wire:model="form.image" />
        </div>
        HTML;
    }
}

I then have my image blade component that is responsable for showing the current image and the new image

<label >
    <input wire:model="{{ $attributes->wire('model')->value() }}" type="file" class="hidden" accept=".svg,.png,.jpg,.jpeg,.gif" />

    @if ($image)
        @if (is_string($image))
            <img src="{{ Storage::url($image) }}" class="absolute inset-0 aspect-auto w-full lg:max-h-64 lg:w-auto">
        @elseif ($image instanceof LivewireTemporaryUploadedFile)
            <img src="{{ $image->temporaryUrl() }}" class="absolute inset-0 aspect-auto w-full lg:max-h-64 lg:w-auto">
        @endif
    @endif
</label>

I have tried multiple ways of getting the image to the component… The most success ive had is with the x-data="image: @entange($attributes->wire(‘model’)" but i can seem to find a way to allow it to update with the value changes

2

Answers


  1. You’re creating a reusable image upload component with Livewire. You want to display the current image and the newly uploaded image.

    To achieve this, you’ll need to:

    • Pass the current image URL to the component.

    • Update the component when a new image is uploaded.

      Here’s an updated version of your code:
      CreateStall.php (Livewire Component)

      class CreateStall extends Component
      {
          use WithFileUploads;
          public $image; // Add a public property for the image
          public editStallForm $form;
      
          public function render()
          {
              return <<<'HTML'
              <div> 
                 <x-inputs.image wire:model="image" :currentImage="$image" />
              </div>
              HTML;
          }
      
          public function updatedImage()
          {
              // Update the image property when a new image is uploaded
              $this->form->image = $this->image;
          }
      }
      

      image.blade.php (Component)

      @props(['currentImage'])
      
      <label>
          <input 
              wire:model="{{ $attributes->wire('model')->value() }}" 
              type="file" 
              class="hidden" 
              accept=".svg,.png,.jpg,.jpeg,.gif"
              x-data="imageData(@entangle($attributes->wire('model')->value()), '{{ $currentImage }}')"
              x-init="updateImageDisplay()"
              x-on:change="updateImageDisplay"
          />
      
          <div x-show="image">
              @if ($currentImage)
                  <img :src="$currentImage" class="absolute inset-0 aspect-auto w-full lg:max-h-64 lg:w-auto">
              @elseif (image instanceof LivewireTemporaryUploadedFile)
                  <img :src="image.temporaryUrl()" class="absolute inset-0 aspect-auto w-full lg:max-h-64 lg:w-auto">
              @endif
          </div>
      
          <script>
              function imageData(image, currentImage) {
                  return {
                      image,
                      currentImage,
                      updateImageDisplay() {
                          if (this.image instanceof LivewireTemporaryUploadedFile) {
                              this.currentImage = this.image.temporaryUrl();
                          } else if (this.image) {
                              this.currentImage = '{{ Storage::url('') }}' + this.image;
                          }
                      }
                  }
              }
          </script>
      </label>
      

      Explanation

    • We added a currentImage prop to the image component to display the initial image.

    • In the CreateStall component, we updated the image property when a new image is uploaded using the updatedImage method.

    • We use Alpine.js to manage the image display in the image component. We create an imageData function that takes the image and currentImage as arguments.

    • We used x-data to initialize the component with the imageData function.

    • We used x-on:change to update the image display when a new image is uploaded.

    • We used x-show to display the image conditionally.

      This should solve the issue of updating the image display when a new image is uploaded.

    Update:

    The issue is due to the execution order in Alpine.js.
    The x-data directive is executed after the template has been rendered, which means the image is undefined when the template is first rendered.
    To fix this, you can use a slight modification to your code:

    @props(['currentImage'])
    
    <label 
        x-data="imageData({{ $currentImage }})"
        x-init="updateImageDisplay"
        x-on:change="updateImageDisplay"
    >
        <input 
            wire:model="{{ $attributes->wire('model')->value() }}" 
            type="file" 
            class="hidden" 
            accept=".svg,.png,.jpg,.jpeg,.gif"
            x-ref="fileInput"
        />
    
        <div x-show="image">
            <img :src="image" class="absolute inset-0 aspect-auto w-full lg:max-h-64 lg:w-auto">
        </div>
    
        <script>
            function imageData(currentImage) {
                return {
                    image: currentImage,
                    updateImageDisplay() {
                        const fileInput = this.$refs.fileInput;
                        if (fileInput.files.length > 0) {
                            const file = fileInput.files[0];
                            this.image = URL.createObjectURL(file);
                        } else {
                            this.image = this.image || '{{ Storage::url('') }}' + currentImage;
                        }
                    }
                }
            }
        </script>
    </label>
    
    Login or Signup to reply.
  2. The issues you’re experiencing are due to:

    Alpine.js rendering the :src attribute as src and then Livewire updating the image with a temporary URL, resulting in duplicate src attributes.
    x-data not calling imageData function.

    To resolve these issues:

    1. Duplicate src attribute
      Use x-bind:src instead of :src to bind the src attribute:
      PHP

      <img x-bind:src="image" class="absolute inset-0 aspect-auto w-full lg:max-h-64 lg:w_auto">

    2. x-data not calling imageData function
      You’re passing a string to x-data, but it expects a JavaScript object or function.
      Try changing x-data to:
      PHP

      x-data="{ imageData: imageData(@json($currentImage)) }"

    Or, for better readability:
    PHP

    x-init="imageData(@json($currentImage))"
    x-data="{ image: null, updateImageDisplay() { /* ... */ } }"`
    

    Then, define the imageData function inside the x-data object:
    PHP

    <script>
    function imageData(currentImage) {
        return {
            image: currentImage,
            updateImageDisplay() {
                // ...
            }
        }
    }
    

    However, I recommend simplifying your code. You don’t need the imageData function. Instead, initialize the image directly:
    PHP

    x-data="{ image: @json($currentImage) }"
    

    x-init="updateImageDisplay"
    x-on:change="updateImageDisplay"

    And update the updateImageDisplay function accordingly.
    Full updated code
    PHP

    @props(['currentImage'])
    
    <label 
    x-data="{ image: @json($currentImage) }"
    x-init="updateImageDisplay"
    x-on:change="updateImageDisplay"
    >
    <input 
        wire:model="{{ $attributes->wire('model')->value() }}" 
        type="file" 
        class="hidden" 
        accept=".svg,.png,.jpg,.jpeg,.gif"
        x-ref="fileInput"
    />
    
    <div x-show="image">
        <img x-bind:src="image" class="absolute inset-0 aspect-auto w-full lg:max-h-64 lg:w-auto">
    </div>
    
    <script>
        function updateImageDisplay() {
            const fileInput = this.$refs.fileInput;
            if (fileInput.files.length > 0) {
                const file = fileInput.files[0];
                this.image = URL.createObjectURL(file);
            } else {
                this.image = this.image || '{{ Storage::url('') }}' + @json($currentImage);
            }
        }
    </script>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search