Lopsum's avatar

Issues with file upload

Hello all !👋 I’m trying to learn Laravel Livewire and its file upload part.(https://laravel-livewire.com/docs/2.x/file-uploads) For this, I made an application with a droparea for an Excel file.

Here, you can retrieve the code :

<div x-data="dropFile()">
    <div x-show="error" class="alert alert--danger alert--icon">
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
            <path stroke-linecap="round" stroke-linejoin="round" d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
        </svg>
        <p x-text="errorMsg"></p>
    </div>
    @error('file')
    <div class="alert alert--danger alert--icon">
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
            <path stroke-linecap="round" stroke-linejoin="round" d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
        </svg>
        <p>{{ $message }}</p>
    </div>
    @enderror


    <div
        id="dropzone"
        x-bind:class="dropingFile ? 'dropzone is-highlighted' : 'dropzone'"
        x-on:drop="dropingFile = false"
        x-on:drop.prevent="handleFileDrop($event)"
        x-on:dragover.prevent="dropingFile = true"
        x-on:dragleave.prevent="dropingFile = false"
        x-on:livewire-upload-start="isUploading = true"
        x-on:livewire-upload-finish="isUploading = false"
        x-on:livewire-upload-error="isUploading = false"
        x-on:livewire-upload-progress="progress = $event.detail.progress"
    >
        <form wire:submit.prevent="saveFile"  enctype="multipart/form-data">
            <input type="file" name="file" wire:model="file">
        </form>
        <h3>
            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
                <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m3.75 9v6m3-3H9m1.5-12H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
            </svg>
            Drop a .csv or xls file here
        </h3>
    </div>
    <div x-show="isUploading">
        <progress max="100" x-bind:value="progress"></progress>
    </div>
</div>


<script>
    const dropFile = () => {
        return {
            dropingFile: false,
            isUploading: false,
            error: false,
            errorMsg: '',
            progress: 0,
            handleFileDrop(e) {
                this.error = false;
                if (e.dataTransfer.files.length > 0) {
                    const file = e.dataTransfer.files;
                    if (e.dataTransfer.files.length > 1) {
                        this.error = true;
                        this.errorMsg = 'You can only import one file.';
                    } else if
                    (
                        e.dataTransfer.files[0].type !== "application/vnd.ms-excel" &&
                        e.dataTransfer.files[0].type !== "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
                        this.error = true;
                        this.errorMsg = "You can only import a CSV or XLS file";
                    } else {
                        @this.upload('file', file[0], (uploadedFilename) => {
                            this.isUploading = false;
                        }, () => {
                            console.log('error');
                        }, (event) => {
                            this.isUploading = true;
                            this.progress = event.detail.progress;
                        })
                    }
                }
            }
        }
    }
</script>

As you can see, I use the droparea to drag and drop a file (with AlpineJS), or if you click on the area, it will trigger the input[type="file"].

The component’s Livewire code is:

<?php

namespace App\Http\Livewire;

use Livewire\Component;
use Livewire\WithFileUploads;

class UploadExcel extends Component
{

    use WithFileUploads;
    public $file = [];

    protected $rules = [
        'file' => 'required|file|mimes:csv,xlsx,xls|max:102400',
    ];


    public function updated($propertyName)
    {
        $this->validateOnly($propertyName);
    }

    public function saveFile()
    {
        $this->validate();
        $this->file->store('csv', 'local');
    }

    public function render()
    {
        return view('livewire.upload-excel');
    }
}

My problems are :

  • The validation does not work. Whether it is via input or drag and drop. I am told that the file field must be a file.
  • If I remove the "file" in $rules, I am told that the file type is not the right one, although I do have an Excel file.
  • If I remove the rules, the file is present in the "livewire-tmp" folder, but nothing on the final disk.

I'm in local, on Windows with Laragon. The storage:link on storage folder is good, permissions also.

I’m starting with Livewire and wondering if I missed something... I don’t understand why validation fails every time and why the file is not transferring from temporary folder to final folder.

Thank you for your help😇

0 likes
12 replies
Sinnbeck's avatar

Can you try removing wire:model="file" from the input ? I don't assume you use it? And @this.upload('file', file[0], (uploadedFilename) already triggers the updated() method.

1 like
Lopsum's avatar

@OussamaMater Thank you, I’ll watch right now!

@sinnbeck The wire:model is not to trigger the upload on the component ? If I remove it, There’s nothing going on when I click on the input. For the @this.upload, I thought it was for the javascript upload, since I was using AlpineJS for drag and drop. Isn’t that right? If I take it off, nothing happens to Livewire.

Lopsum's avatar

@Sinnbeck Yeah, I guess there’s probably still a few people who aren’t comfortable with drag and drop so... 😅 I’m watching the tutorial (and thank you in advance for making it) and I’ll come back to you if I made it!

Sinnbeck's avatar

@Vable Its just a blog post, but you can just take the code that you need :) I use it myself.

Lopsum's avatar

I made some adaptations following your tutorial but the final storage does not work... 🤔 Is it necessary to surround the component with a form for the upload to be functional? In fact, the file is sent to the livewire-tmp folder file but it is not moved to the final folder.

Here my code :

UploadExcel :

<?php

namespace App\Http\Livewire;

use Livewire\Component;
use Livewire\WithFileUploads;

class UploadExcel extends Component
{

    use WithFileUploads;
    public $file = [];

    public function save()

    {
        $this->file->storeAs('excel', 'excel');

    }


    public function render()
    {
        return view('livewire.upload-excel');
    }
}

The livewire componant view :

<div x-data="excelUpload()">
    <div x-show="error" class="alert alert--danger alert--icon">
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
            <path stroke-linecap="round" stroke-linejoin="round" d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
        </svg>
        <p x-text="errorMsg"></p>
    </div>
    <div
        id="dropzone"
        x-bind:class="isDropping ? 'dropzone is-highlighted' : 'dropzone'"
        x-on:drop="isDropping = false"
        x-on:drop.prevent="handleFileDrop($event)"
        x-on:dragover.prevent="isDropping = true"
        x-on:dragleave.prevent="isDropping = false"
    >
        <h3>
            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
                <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m3.75 9v6m3-3H9m1.5-12H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
            </svg>
            Déposez un fichier .csv ou xls
        </h3>
        <input type="file" id="file-upload" @change="handleFileSelect">
    </div>
    <div class="progressBar" x-show="isUploading">
        <div class="bar" :style="`width: ${progress}%;`"></div>
    </div>
</div>

<script>
    const excelUpload = () =>
    {
        return {
            isDropping: false,
            isUploading: false,
            error: false,
            errorMsg: '',
            progress: 0,
            handleFileSelect(event) {
                this.error = false;
                const file = event.target.files;
                if (file.length > 1)
                {
                    this.error = true;
                    this.errorMsg = "You can only import one file at a time.";
                } else if(
                    file[0].type !== "application/vnd.ms-excel" &&
                    file[0].type !== "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                ) {
                    this.error = true;
                    this.errorMsg = "You can only import a CSV or XLS file.";
                } else
                {
                    this.uploadFiles(file[0]);
                }
            },
            handleFileDrop(event) {
                const file = event.dataTransfer.files;
                if (file.length > 1)
                {
                    this.error = true;
                    this.errorMsg = 'You can only import one file at a time.';
                } else if(
                    file[0].type !== "application/vnd.ms-excel" &&
                    file[0].type !== "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                ) {
                    this.error = true;
                    this.errorMsg = "You can only import a CSV or XLS file.";
                } else
                {
                    this.uploadFiles(file[0]);
                }

            },
            uploadFiles(file)
            {
                const $this = this
                this.isUploading = true
                @this.upload('file', file,
                    function(success)
                    {
                        $this.isUploading = false
                        $this.progress = 0
                    },
                    function(error)
                    {
                        console.log('error', error)
                    },
                    function (event) {
                        $this.progress = event.detail.progress
                    }
                )

            }
        }
    }
</script>
Sinnbeck's avatar

@Vable Try giving it the full path

$this->file->storeAs(storage_path('app), 'excel.xlsx');

Or give it the disk as 3rd parameter

$this->file->storeAs('',  'excel.xlsx', 'local');
Lopsum's avatar

@Sinnbeck It doesn't work. I tried these three methods :

$this->file->storeAs('', 'excel.xlsx', 'local');

$this->file->storeAs(storage_path('app'), 'excel.xlsx');

Storage::disk('public')->put('excel.xls', $this->file);

No errors on the console or in Laravel Telescope 😥 The save method is automatically trigger by Livewire ? Or may I need to call it somewhere ?

Sinnbeck's avatar
Sinnbeck
Best Answer
Level 102

@Vable I notice that you dont have anything to call the save() method in the shown html?

<button type="button" wire:click="save">Save</button>
Lopsum's avatar

@sinnbeck Oh ok I see... Instead, to do without a button, I used events to call my function. Thanks a lot for your help 😀

Please or to participate in this conversation.