File input in svelte does not update data

Viewed 2021

I'm trying to make a site with client-side file input, where some data is processed entirely on the client and nothing sent to the server.

I have the following Svelte component (App.svelte):

<script>
let data;
function handleFileSelect(evt) {
    var files = evt.target.files;
    if (! (files && files[0])) {
        return;
    }
    let binfile = files[0];
    let reader = new FileReader();
    reader.onload = function(evt) {
        data = new Uint8Array(evt.target.result);
    }
    reader.readAsArrayBuffer(binfile);
}
</script>

<main>
    <label for="fileinput">Your data</label>
    <input type="file" id="fileinput" on:click={handleFileSelect}/>
    <div>
        <textarea id="datafield" rows="10" cols="50">{data}</textarea>
    </div>
</main>

When I click the button, the data is not updated in the text area, but when I click the button a second time, the data is loaded into the text area as soon as it's clicked. Am I on the right track for loading a file in svelte? What am I missing? Thanks!

3 Answers

On a file input you want to bind the value(s) not the click event.

Like so:

<script>
let files = [];
<input 
   bind:files
   type="file"
   accept="image/*">

or bind:value={files}

{files}

EDIT: to answer the question of chovy

let template = null;
let files = null;

$: if (files) {
   const fileText = files[0].text();
   fileText.then((text) => {
       template = text;
   });
   files = null;
}

Please be aware that I only intended to use the first file in my code, if you're using multi file upload you'd have to remove [0] from the files call, iterate over the array and write the contents of each individual file into the designated variables as you would do with any other array data. In the case above template would contain the actual file data as text.

Hope this helps.

I also had to do this to get the contents of the file:

<script>
let data;
let files;
$: {
    if (files && files[0]) {
        let binfile = files[0];
        let reader = new FileReader();
        reader.onload = function(evt) {
            data = new Uint8Array(evt.target.result);
        }
        reader.readAsArrayBuffer(binfile);
    }
}
</script>

<main>
    <label for="fileinput">Your data</label>
    <input type="file" id="fileinput" bind:files />
    <div>
        <textarea id="datafield" rows="10" cols="50">{data}</textarea>
    </div>
</main>

If you want a custom button instead of the ugly default button: Code is svelte + Framework7

<script>
let browseInput;
let data;
let files;

$: {
    if ... see answer 2 
   }
</script>

<input bind:this={browseInput} type="file" id="fileinput" class="hidden" bind:files={files}>
<Button onClick={() => browseInput.click()} fill raised">Open Keystore File</Button>

<style>
.hidden {
   display: none !important;
 }
</style>
Related