I need to create a form that will display something based on the return value of an API. I'm working with the following code:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value); //error here
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} /> // error here
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
I'm getting the following error:
error TS2339: Property 'value' does not exist on type 'Readonly<{}>'.
I got this error in the two lines I commented on the code. This code isn't even mine, I got it from the react official site (https://reactjs.org/docs/forms.html), but it isn't working here.
Im using the create-react-app tool.
The Component
is defined like so:
interface Component<P = {}, S = {}> extends ComponentLifecycle<P, S> { }
Meaning that the default type for the state (and props) is: {}
.
If you want your component to have value
in the state then you need to define it like this:
class App extends React.Component<{}, { value: string }> {
...
}
Or:
type MyProps = { ... };
type MyState = { value: string };
class App extends React.Component<MyProps, MyState> {
...
}
interface MyProps {
...
}
interface MyState {
value: string
}
class App extends React.Component<MyProps, MyState> {
...
}
// Or with hooks, something like
const App = ({}: MyProps) => {
const [value, setValue] = useState<string>('');
...
};
type
's are fine too like in @nitzan-tomer's answer, as long as you're consistent.
If you don't want to pass interface state or props model you can try this
class App extends React.Component <any, any>
I suggest to use
for string only state values
export default class Home extends React.Component<{}, { [key: string]: string }> { }
for string key and any type of state values
export default class Home extends React.Component<{}, { [key: string]: any}> { }
for any key / any values
export default class Home extends React.Component<{}, { [key: any]: any}> {}
The problem is you haven't declared your interface state replace any with your suitable variable type of the 'value'
interface AppProps {
//code related to your props goes here
}
interface AppState {
value: any
}
class App extends React.Component<AppProps, AppState> {
// ...
}
According to the official ReactJs documentation, you need to pass argument in the default format witch is:
P = {} // default for your props
S = {} // default for yout state
interface Component<P = {}, S = {}> extends ComponentLifecycle<P, S> { }
Or to define your own type like below: (just an exp)
interface IProps {
clients: Readonly<IClientModel[]>;
onSubmit: (data: IClientModel) => void;
}
interface IState {
clients: Readonly<IClientModel[]>;
loading: boolean;
}
class ClientsPage extends React.Component<IProps, IState> {
// ...
}
typescript and react whats react component P S mean
how to statically type react components with typescript
event.target
is of type EventTarget
which doesn't always have a value. If it's a DOM element you need to cast it to the correct type:
handleChange(event) {
this.setState({value: (event.target as HTMLInputElement).value});
}
This will infer the "correct" type for the state variable as well though being explicit is probably better
My solution
import React, { ChangeEvent, FormEvent } from "react";
interface NameFormProps {
}
interface NameFormState {
value: string
}
export default class NameForm extends React.Component<NameFormProps, NameFormState> {
constructor(props: any) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event: ChangeEvent<HTMLInputElement>) {
this.setState({value: event.target.value});
}
handleSubmit(event: FormEvent<HTMLFormElement>) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
Success story sharing