I have a simple react component that I created with typescript and I encountered the following strange error. Here is my code.
interface State {
value: string
}
class App extends React.Component<{}, State> {
constructor() {
super();
this.state = {
value: ''
}
}
changeHandler = (e: any) => {
let state = Object.assign({}, this.state);
state.value = e.target.value;
this.setState(state);
}
render() {
return (
<div className="App">
<input
type="text"
value={this.state.value}
name="value"
onChange={this.changeHandler} />
</div>
);
}
}
export default App;
And this is the error that I got.
error TS2540: Cannot assign to 'value' because it is a constant or a read-only property.
This error got me thinking that perhaps this was typescript's way of enforcing the rule of not mutating state. To test this theory I did the following.
this.state.value = e.target.value
In this case I am clearly mutating state directly and sure enough I get the same error.
Then I had the idea of changing my interface to this.
interface State {
value: Ivalue
}
interface Ivalue {
value: string;
}
And then I refactored my component to use this new interface like this.
class App extends React.Component<{}, State> {
constructor() {
super();
this.state = {
value: {
value: ''
}
}
}
changeHandler = (e: any) => {
let value = Object.assign({}, this.state.value);
value.value = e.target.value;
this.setState({value});
}
render() {
return (
<div className="App">
<input
type="text"
value={this.state.value.value}
name="value"
onChange={this.changeHandler} />
</div>
);
}
}
export default App;
And sure enough this compiled!
My question is really 2 questions. First, why was typescript not satisfied with my copy of state, like I have in my first snippet of code where I used Object.assign
?
Second, why does nesting my state object one level further down solve this issue?
You can use mapping modifiers to change a readonly property to mutable in TypeScript, e.g. -readonly [Key in keyof Type]: Type[Key] . You can remove the readonly modifier by prefixing the readonly keyword with a minus - .
TypeScript includes the readonly keyword that makes a property as read-only in the class, type or interface. Prefix readonly is used to make a property as read-only. Read-only members can be accessed outside the class, but their value cannot be changed.
If the internal implementation (or TypeScript's understanding thereof) of Object.assign
is to copy the object members' descriptors, and not just the field names, then it's copying the readonly
attribute of your state.value
and not just the keyname of your state.value
.
I can't guarantee that's actually what's happening, as this would be news to me. But it's actually good news, and not bad news.
What versions of things are you using?
Also, you'll want to get into the habit of using the functional form of setState
, if you're going to continue to use it through the next major release.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With