Search code examples
javascriptsveltesveltekitsvelte-3svelte-component

Svelte Simple form with file upload not working


I'm using svelte and svelte-forms-lib. I've got a form component that looks like:

<script>

    import { createForm } from "svelte-forms-lib";

    export let mode;
    export let submitFn;
    export let formData = {};

    let fileInput = "";
    let imagePreview = "";

    const handleDrop = (e) => {
        e.preventDefault();
        e.stopPropagation();
        const file = e.dataTransfer.files[0];
        handleImage(file)
    }

    const handleImage = imageFile => {
        if (imageFile) {
            const fileURL = URL.createObjectURL(imageFile)
            const reader = new FileReader();
            reader.onload = readerEvent => {
                formData.image = readerEvent.target.result;
            }
            imagePreview = fileURL;
        }
    }

    const clearImagePreview = () => {
        imagePreview = "";
        formData.image = "";
    }

    const { form, handleChange, handleSubmit } = createForm({
        initialValues: formData,
        onSubmit: async data => {
            console.log("submitting form")
            console.log(data)
        }
    })

</script>

<form on:submit|preventDefault={handleSubmit} enctype="multipart/form-data">
{#if imagePreview === ""}
                        <label for="dropzone-file" class="flex flex-col justify-center items-center w-full h-64 bg-gray-50 rounded-lg border-2 border-gray-300 border-dashed cursor-pointer dark:hover:bg-bray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600">
                            <div class="flex flex-col justify-center items-center pt-5 pb-6" on:drop={handleDrop} ondragover="return false">
                                <svg aria-hidden="true" class="mb-3 w-10 h-10 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path></svg>
                                <p class="mb-2 text-sm text-gray-500 dark:text-gray-400"><span class="font-semibold">Click to upload</span> or drag and drop</p>
                                <p class="text-xs text-gray-500 dark:text-gray-400">SVG, PNG, JPG or GIF (MAX. 800x400px)</p>
                            </div>
                            <input
                                    id="dropzone-file"
                                    bind:files={formData.image}
                                    name="image"
                                    on:change={ e => handleImage(e.target.files[0])}
                                    type="file"
                                    class="hidden" />
                        </label>

                    {:else}
                        <img src={imagePreview}
                             class="flex flex-col justify-center items-center w-full h-64 bg-gray-50 rounded-lg border-2 border-gray-300 border-dashed dark:hover:bg-bray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600" />
                        <a href="#" on:click={clearImagePreview}>Remove</a>
                    {/if}

<div class="grid grid-cols-1">
                <div class="p-4  flex item-center justify-center">
                    <button type="submit" class="btn btn-active btn-primary">
                        { mode == "new" ? "Save" : "Update" }
                    </button>
                </div>
            </div>

</form>

When I click the Save button, I don't see the data for my image in the object. Basically I've identified 2 issues:

  1. reader.onload doesn't seem to be fired. If I add logs within reader.onload they never appear on the console.

  2. If I run a method from the reader, let's say reader.readAsDataURL(imageFile):

const handleImage = imageFile => {

        if (imageFile) {
            const fileURL = URL.createObjectURL(imageFile)
            const reader = new FileReader();
            const dataAsURL = reader.readAsDataURL(imageFile);

            reader.onload = readerEvent => {
                console.log("here in reader.onload")
                formData.image = readerEvent.target.result;
                console.log("reader event...");
                console.log(formData.image);
            }
            imagePreview = fileURL;
        }

I do see the logs from reader.onload. Furthermore, I can see that formData.image is being populated with the base64 representation of the image BUT when I click the submit button the formData object does not have the image property.

For simplicity I've omitted all other fields in the form, most of them of type text, but they do appear when the form is submitted. For instance:

<input
                                id="barcode"
                                bind:value={formData.barcode}
                                on:change={handleChange}
                                type="text"
                                class="input w-full py-4 font-medium bg-gray-100 border-gray-200 text-sm
                  focus:outline-none focus:border-gray-400 focus:bg-white"
                        />

is being submitted.

What's going on here?


Solution

  • When creating the form, the library will make a deep copy of the initialValue [code]
    So changing formData doesn't have any effect on data in onSubmit.

    If you look at the documentation the values of the input fields are bind to a $form store which is destructured from createForm

    bind:value={$form.title}
    

    Your other input elements don't work because of the value binding to formData but because of the on:change={handleChange} that does update $form as well.

    To make your example work I'd

    • change to bind:files={$form.imgFiles}
    • remove the on:change on the file input
    • read the file inside onSubmit
    • display the preview Image via a reactive variable
      $: previewImgSrc = $form.imgFiles ? URL.createObjectURL($form.imgFiles[0]) : ''