Search code examples
reactjstypescriptinputmethodsmaterial-ui

Property 'files' is missing in type 'EventTarget & HTMLTextAreaElement'


I am trying to call a method in React TypeScript on the onChange Event of a MUI Input field.

Unfortunately I get the following Error on the onChange:

Type '(event: { target: { files: any[]; }; }) => void' is not assignable to type 'ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement>'. Types of parameters 'event' and 'event' are incompatible. Type 'ChangeEvent<HTMLTextAreaElement | HTMLInputElement>' is not assignable to type '{ target: { files: any[]; }; }'. Types of property 'target' are incompatible. Type 'EventTarget & (HTMLTextAreaElement | HTMLInputElement)' is not assignable to type '{ files: any[]; }'. Property 'files' is missing in type 'EventTarget & HTMLTextAreaElement' but required in type '{ files: any[]; }'.

I'm trying to access the state and later-on call the method by onChange.

Also important for you guys that I don't have much experience in File Upload and Save in React TypeScript with my API in JS it works but in TypeScript not I also went through 100 Tutorials but nothing helped me.

import axios from "axios";
import React from "react";
import { Input } from "@mui/material";
// export class FileUpload extends Component {
const FileUpload: React.FC = () => {
    
    const [selectedFile] = React.useState<Blob>(new Blob);
    // const [users] = React.useState<Blob | string>("");


    function onFileChange(event: { target: { files: any[]; }; }): void {
    // function onFileChange(event: { target: { files: any[]; }; }): void {
        React.useState({ selectedFile: event.target.files[0] });
    }

    // On file upload (click the upload button)
    async function onFileUpload() {
        // Create an object of formData
        const formData = new FormData();
        // Update the formData object
        formData.append(
            "File",
            selectedFile,
            selectedFile.text.toString()
        );
        // Details of the uploaded file
        console.log(selectedFile);
        // Request made to the backend api
        // Send formData object
        try {
            const responseData = await axios.post("https://localhost:7047/File/Upload", formData);
            console.log(responseData);
        } catch (ex) {
            console.log(ex);
        }
    }

    return (
        <div>
            <Input onChange={onFileChange} />
            <Input type="button" value="upload" onClick={onFileUpload} />
        </div>
    );
};
export default FileUpload;

Solution

  • Material UI declares the type of the onChange prop as:

    onChange?: React.ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement> | undefined
    

    You can discover this by mousing over onChange and you will see the reported type.

    That mean that it handles a text area, or an input tag. This means your event handler needs to handle both as well. And the problem here is that only a HTMLInputElement with a type of file will have a files property.

    And in your code, if files was missing, it would crash.


    So first you need to declare the event type properly:

    function onFileChange(event: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>): void {
      //...
    }
    

    Then you need to make sure the files property exists before you use it.

    function onFileChange(event: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>): void {
        if ('files' in event.target) {
            React.useState({ selectedFile: event.target.files?.[0] });
        } 
    }
    

    Now you only access the files it it exists, and you use the ?. to only drill into if it is not null or undefined.

    Playground with no type type errors


    Lastly, the usage of useState conditionally not correct here. You must follow the rules of hooks when using hooks like useState and that will look more like this:

    const [selectedFile, setSelectedFile] = React.useState<Blob>(new Blob);
    
    function onFileChange(event: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>): void {
        if ('files' in event.target) {
            setSelectedFile(event.target.files?.[0]);
        }
    }