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
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>
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:
-
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">
-
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:
PHPx-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>