I would just store the file objects, no object wrappers or anything:
const [inputTattoos, setInputTattoos] = useState<File[]>([]);
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^−−^^
const handleImageChange = ({
currentTarget: {files},
}: React.ChangeEvent<HTMLInputElement>) => {
if (files && files.length) {
setInputTattoos(existing => [...existing, ...files]);
// −−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}
// ...
}
A couple of notes on that:
I've removed async. Change handlers aren't async functions, and nothing is going to use the promise an async function would return.
I've destructured the files property into its own parameter, so that TypeScript knows it can't change between the guard and the state setter callback. (That's also important because you're not supposed to access properties from React's synthetic event objects asynchronously unless you call persist.)
I've used the callback version of the state setter. That's important when setting a state item based on its existing value (in this case, the previous contents).
The above relies on files (from the input) being iterable, which it is in modern browsers but not in some slightly-older browsers.
Re #4, if you need to work around that for slightly-older browsers:
const [inputTattoos, setInputTattoos] = useState<File[]>([]);
const handleImageChange = ({
currentTarget: {files},
}: React.ChangeEvent<HTMLInputElement>) => {
if (files && files.length) {
setInputTattoos(existing => existing.concat(Array.from(files))); // *** Only change is here
}
// ...
}
The change there is the callback:
existing => existing.concat(Array.from(files))
Note that since files is a FileList, not an array, we need to convert it to an array for concat to handle it properly.
Array.from is just a couple of years old but easily polyfilled; if you don't want to do that, here's an alternative using nothing modern (other than the arrow function):
existing => existing.concat(Array.prototype.slice.call(files))
Here's a complete example using Array.from for that part:
const { useState } = React;
function Example() {
const [inputTattoos, setInputTattoos] = useState/*<File[]>*/([]);
const [inputKey, setInputKey] = useState(0);
const handleImageChange = ({
currentTarget: {file},
}/*: React.ChangeEvent<HTMLInputElement>*/) => {
if (files && files.length) {
setInputTattoos(existing => existing.concat(Array.from(files)));
}
// Reset the input by forcing a new one
setInputKey(key => key + 1);
}
return (
<form>
Selected tattoos ({inputTattoos.length}):
<ul>
{inputTattoos.map((file, index) =>
<li key={index}>{file.name}</li>
)}
</ul>
Add: <input key={inputKey} type="file" onChange={handleImageChange} />
</form>
);
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
Here's a running TypeScript version.