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

Zayar's avatar
Level 11

File input blade components using Alpine Js is clashing

I have a file input blade component that uses alpine js for uploading the image file to Livewire while using the Livewire file upload events for user feedback.

I am trying to create the file input something like filepond using alpine js

Everything works as long as I use the file-input component once.

<div>
    <x-file-input wire:model="icon" label="Browser"></x-file-input>
</div>

But if I use it twice in the same file like below, the files are getting all mixed up

<div>
    <x-file-input wire:model="icon" label="Browse"></x-file-input>
</div>

<div class="mt-10">
    <x-file-input wire:model="coverImage" label="Browse some other file"></x-file-input>
</div>

How do I separate these two components so that they don't clash with each other?

File Input component

<label
	x-data="fileUploader()"
	for="icon"
	class="bg-gray-500 rounded-md w-full min-h-10 flex items-center justify-center text-white font-semibold overflow-hidden"
>
	<span x-show="!isUploading && !isUploaded">{{ $label }}</span>
	<div x-show="isUploading || isUploaded"  class="bg-black w-full flex justify-center relative">
		<div class="absolute left-0 top-0 mx-3 mt-2 flex flex-col">
			<span class="text-white text-xs" x-text="name"></span>
			<span class="text-white text-xs" x-text="size"></span>
		</div>

		<div class="absolute flex items-start mt-2 mx-3 right-0 top-0">
			<div class="inline-flex mr-3">
				<p class="leading-none">
					<span class="text-white text-xs" x-text="statusMessage"></span>
					<span x-show="isUploading" x-cloak class="text-white text-xs" x-text="progress"></span>
				</p>
			</div>
			<div class="inline-flex items-center">
				<svg x-show="isUploading" x-cloak class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
					<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
					<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
				</svg>
				<button type="button" x-show="isUploaded" x-on:click="removeUpload" x-cloak  class="bg-opacity-25 bg-white rounded-full shadow-inner" style="padding: 5px">
					<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
					</svg>
				</button>
			</div>
		</div>

		<img wire:ignore class="h-48 object-cover" :src="previewSource">
	</div>

	<input x-ref="input" x-on:change="uploadImage" class="hidden" type="file" id="icon">
</label>
<script>
	function fileUploader() {
		return {
			file: @entangle($attributes->wire('model')),
			uploadedFilename: '',
			previewSource: '',
			statusMessage: '',

			isUploading: false,
			isUploaded: false,
			progress: 0,
			name: '',
			size: '',

			uploadImage(event) {
				const fileList = event.target.files
				this.previewSource = URL.createObjectURL(fileList[0]);
				this.statusMessage = 'Uploading';
				this.name = fileList[0].name;
				this.bytesForHuman(fileList[0].size);
				this.isUploading = true

				@this.upload('{{ $attributes['wire:model'] }}', fileList[0], (uploadedFilename) => {
					this.uploadedFilename = uploadedFilename
					this.isUploading = false
					this.isUploaded = true
					this.statusMessage = 'Upload complete'
					event.target.value = null
				}, () => {
					// on error
				}, (event) => {
					this.progress = event.detail.progress
				})
			},

			removeUpload() {
				this.previewSource = ''
				@this.removeUpload('{{ $attributes['wire:model'] }}', this.uploadedFilename, () => {
					this.isUploaded = false
					this.uploadedFilename = ''
					this.progress = 0
					this.name = ''
					this.size = ''
					this.statusMessage = ''
				})
			},

			bytesForHuman(bytes) {
				let units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
				let i = 0
				for (i; bytes > 1024; i++) {
					bytes /= 1024;
				}
				this.size = bytes.toFixed(1) + ' ' + units[i]
			}
		}
	}
</script>
0 likes
1 reply
Zayar's avatar
Level 11

After hours of pain, finally found the problem. The problem was wrapping file input within the label tag and when input is set to hidden, don't know what to call the problem but I think it's dom diffing.

If you building a custom file input element, don't wrap it within label instead use a div and add click event listener to fire file input click().

Please or to participate in this conversation.