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