blaker1327
blaker1327

Reputation: 87

Argument type string | ArrayBuffer is not assignable to parameter type string

Getting a TS error in line 15 particularly in e.target.result.

Argument type string | ArrayBuffer is not assignable to parameter type string  Type ArrayBuffer is not assignable to type string

let fileTag = document.getElementById("filetag"),
    preview = document.getElementById("preview");

fileTag.addEventListener("change", function() {
    changeImage(this);
});

function changeImage(input) {
    let reader;

    if (input.files && input.files[0]) {
        reader = new FileReader();

        reader.onload = function(e) {
            preview.setAttribute('src', e.target.result);
        }

        reader.readAsDataURL(input.files[0]);
    }
}

added HTML

<div class="img-container">
<input type="file" id="filetag">
<img src="" class="profImage" id="preview" alt="profilePic">
</div>

Upvotes: 3

Views: 1741

Answers (2)

dumbass
dumbass

Reputation: 27221

(Independently of the actual answer, consider using URL.createObjectURL instead of converting the File to a data: URI; it will save you some resources on spurious string conversions.)


The problem is that type checking works locally.

let preview = document.getElementById("preview");
let reader = new FileReader();

reader.onload = function(e) {
    preview.setAttribute('src', e.target.result);
}

Based on surrounding context, TypeScript knows that preview is a DOM node, and therefore preview.setAttribute expects two string arguments. It also knows that e.target is a FileReader, and therefore its property result may be either a string or an ArrayBuffer. Which one it is depends on the method that was earlier called on the FileReader, but this information is hard to express in a type system and is not available within the event handler anyway. For all the type checker knows, the event handler may have been invoked after readAsArrayBuffer was called somewhere far, far away on the same FileReader object.

Since in this case you know better than the type checker, you can use a type assertion to convince it that the value is indeed a string:

reader.onload = function(e) {
    preview.setAttribute('src', e.target.result as string);
}

If you don’t want to litter your code with type assertions everywhere, consider wrapping your code in an abstraction that is easier to type check, for example like this:

function readFileAsDataURL(file): Promise<string> {
    return new Promise((accept, reject) => {
        const reader = new FileReader();
        reader.onload = function (ev) {
            accept(ev.target.result as string);
        };
        /* XXX: rejecting with an event is rather unorthodox */
        reader.onabort = reader.onerror = function (ev) {
            reject(ev);
        }
        reader.readAsDataURL(file);
    });
}

async function changeImage(input) {
    preview.setAttribute('src', await readFileAsDataURL(input.files[0]));
}

Now if you remember to use readFileAsDataURL everywhere in your code instead of constructing FileReaders directly, the only place where a type assertion will be required is inside that one function.

Upvotes: 5

dave
dave

Reputation: 64657

You might be able to do:

function ab2str(buf: ArrayBuffer): string {
    return String.fromCharCode.apply(null, new Uint16Array(buf));
}
// ...
reader.readAsDataURL(typeof input.files[0] === 'string' ? input.files[0] : ab2str(input.files[0]))

Or, if you are positive it will always be a string:

reader.readAsDataURL(input.files[0] as string);

basically if it's a string, just use it, but if it's an ArrayBuffer, first convert it to a string. Not sure if typescript will be able to follow that though.

https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String

Otherwise, just do:

preview.setAttribute('src', e.target.result as string);

again, assuming you are positive it will actually always be a string

Upvotes: -1

Related Questions