Home > Mobile >  Property 'xy' does not exist on type '{}'. TS2339
Property 'xy' does not exist on type '{}'. TS2339

Time:12-12

I'm trying to setup a small project with react / typescript and have the following components:

App.tsx

import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Styles from "./App.module.scss";
import TopBar from "./components/TopBar/TopBar";
import { library } from "@fortawesome/fontawesome-svg-core";
import { fab } from "@fortawesome/free-brands-svg-icons";
import { faUser } from "@fortawesome/free-solid-svg-icons";
import Wrapper from "./components/Wrapper/Wrapper";
import Dashboard from "./components/Dashboard/Dashboard";
import NewTask from "./components/NewTask/NewTask";
import AllTasks from "./components/AllTasks/AllTasks";

const App: React.FC = (props) => {
  library.add(fab, faUser);
  return (
    <div className={Styles.App}>
      <Router>
        <TopBar></TopBar>
        <Wrapper>
          <Routes>
            <Route path="/" element={<Dashboard></Dashboard>}></Route>
            <Route path="/new-task" element={<NewTask></NewTask>}></Route>
            <Route path="/all-tasks" element={<AllTasks></AllTasks>}></Route>
          </Routes>
        </Wrapper>
      </Router>
    </div>
  );
};

export default App;

and a component NewTask.tsx:

import React, { ChangeEvent, useState } from "react";
import Styles from "./NewTask.module.scss";
import TextInputField from "../Globals/commonUI/Input/TextInputField/TextInputField";
import SelectGroup from "../Globals/commonUI/SelectGroup/SelectGroup";
import TextArea from "../Globals/commonUI/Input/TextArea/TextArea";
import Submit from "../Globals/commonUI/Submit/Submit";

interface Properties {
  taskName?: string;
  taskType?: string;
  taskPriority?: string;
  taskDescription?: string;
  taskStatus?: string;
  task?: any[];
}
const NewTask: React.FC<Properties> = () => {
  const [errMsg, setErrMsg] = useState("");
  const [task, setTask] = useState({
    taskName: "",
    taskType: "",
    taskPriority: "",
    taskDescription: "",
    taskStatus: "new",
  });

  const formSubmitHandler = (event: React.ChangeEvent<any>): void => {
    event.preventDefault();

    // Check if one or more inputs have been left empty

    for (let i = 0; i <= 4; i  ) {
      if (event.target.form[i].value === "") {
        setErrMsg("Hey buddy, you've left one or more fields empty!");
        // console.log("ive done this: "   i   " times!");
        return;
      }
    }

    // Inputs have been validated, errMsg is All Fine
    setErrMsg("All Fine!");
    // console.dir(task.task)
    // Post Input Values to API-Endpoint
    const requestOptions = {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(task.task),
    };
    fetch("http://localhost:5000/task-write", requestOptions)
      .then((response) => response.json())
      .then((data) => this.setState({ postId: data.id }));

    // console.dir(task.task);
    // Reset Inputs
    formResetHandler(event);
  };

  const formResetHandler = (event) => {
    for (let i = 0; i <= 4; i  ) {
      event.target.form[i].value = "";
    }
  };

  const inputsChangedHandler = (event) => {
    // console.log("INPUT CHANGED!");
    let target = event.target;
    let name = event.target.name;
    let value = event.target.value;

    setTask((prevState) => ({
      task: {
        ...prevState.task,
        [name]: value,
      },
    }));
  };

  return (
    <div className={Styles.newtask}>
      <div className={Styles.newtask__heading}>
        <h1>New Task</h1>
        <span>Add New Tasks</span>
      </div>
      <div className={Styles.newtask__content}>
        <form onSubmit={formSubmitHandler}>
          <TextInputField
            name="task_name"
            placeholder="Task Name ..."
            onChange={inputsChangedHandler}
          ></TextInputField>
          <TextArea
            onChange={inputsChangedHandler}
            placeholder="Task Description ..."
          ></TextArea>
          <SelectGroup
            onChange={inputsChangedHandler}
            name="task_priority"
            options={["low", "medium", "urgent", "backlog"]}
            default="Priority"
          />
          <SelectGroup
            onChange={inputsChangedHandler}
            name="task_type"
            options={["action", "research", "chore"]}
            default="Type"
          ></SelectGroup>
          <Submit name="Send"></Submit>
        </form>
      </div>
    </div>
  );
};

export default NewTask;

I'm getting Property 'task' does not exist on type '{ taskName: string; taskType: string; taskPriority: string; taskDescription: string; taskStatus: string; }'. TS2339

So my questions are as follows:

  • What is missing here?
  • What has to be done to resolve it
  • What ts-mechanism lies behind that error, so i can understand it properly

Thanks so much!

CodePudding user response:

emphasized textWhen you assign an object to a state variable in React (and don't use a generic, i.e. useState<Properties>, Typescript will assume you want to use that type for the state and enforce it throughout the rest of your code. Your state object initially looks like this:

{
    taskName: "",
    taskType: "",
    taskPriority: "",
    taskDescription: "",
    taskStatus: "new",
  }

but later on you try and set the state:

setTask((prevState) => ({
      task: {
        ...prevState.task,
        [name]: value,
      },
    }));

There is no "task" property of your state object, it should just read

setTask((prevState) => ({
      task: {
        ...prevState,
        [name]: value,
      },
    }));

You'll also have a problem I think indexing using name because your HTML names don't match up with the type names, but hopefully this gets you on the path to understanding how TS expects your state to look.

CodePudding user response:

Line 18, change:

  const [task, setTask] = useState({
    taskName: "",
    taskType: "",
    taskPriority: "",
    taskDescription: "",
    taskStatus: "new",
  });

To

  const [task, setTask] = useState<Properties>({
    taskName: "",
    taskType: "",
    taskPriority: "",
    taskDescription: "",
    taskStatus: "new",
  });

Or, even better yet, in the object literal, include a task field.

  const [task, setTask] = useState<Properties>({
    taskName: "",
    taskType: "",
    taskPriority: "",
    taskDescription: "",
    taskStatus: "new",
    task: [],
  });

The useState function is a generic function. TypeScript often resorts to type inference, if things are not explicit. Which is not a bad thing. In fact, it's what makes TypeScript so much more elegant to write, than languages like Java, C# (at least older versions of C#), C (at least older versions of C ).

The thing about generic functions is that if a function accepts a particular value of the type outlined in the generic parameter, and a parameter was not explicitly provided, then it will infer what type the function parameter would be.

For example, here is a generic function.

function identity<T>(value: T): T {
  return value;
}

In other languages (such as Java), in order to invoke identity, you have to absolutely provide what type the function must accept. But in TypeScript both of the following invocations are correct:

const value1 = identity(10); // Will return a value of type number
const value2 = identity<number>(10); // Same idea

But, the importance of generics is that you can be more explicit in what can be accepted by a function.

For example:

// We want a number

const value = identity('10'); // Will get you a string; not number
const value = identity<number>('10'); // TypeScript will refuse to compile

Sometimes, you want to be explicit.

In your example the Properties type has an optional task field. But, when you invoked useState using that object literal with the task field missing, TypeScript inferred that the task field is not at all a part of the expected type.

Here's what things look like with the identity function:

const obj = identity({
  taskName: "",
  taskType: "",
  taskPriority: "",
  taskDescription: "",
  taskStatus: "new",
});

// The type of `obj` will be pretty much the above object, but with
// the `task` field missing.

obj.task = []; // Compiler error!

To prevent the compiler error, you can do this instead:

const obj = identity<Properties>({
  taskName: "",
  taskType: "",
  taskPriority: "",
  taskDescription: "",
  taskStatus: "new",
});

obj.task = []; // This is fine
  • Related