Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypesScript wrongly assumes that property is read-only

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?

like image 793
Chaim Friedman Avatar asked Jun 22 '17 19:06

Chaim Friedman


People also ask

How do I change a read-only property in TypeScript?

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 - .

Is readonly TypeScript?

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.


1 Answers

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.

like image 98
Norguard Avatar answered Oct 05 '22 16:10

Norguard