1

What is the difference between these two constructs of defining state in React?

class ProductsPage extends Component {
  constructor(props) {
    super(props);
    this.state = {
      products: []
    };
  }
  ...
}

and this:

class ProductsPage extends Component {
  state = {
    products: []
  };
  ...
}

Both of them work well when coded in ES6. However, the lower one doesn't seem to work in typescript 3. I have the following:

interface IState {
  products: IProduct[];
}

class ProductsPage extends Component<{}, IState> {
  state = {
    products: []
  };


  public componentDidMount() {
    this.setState({ products });
  }

  public render() {
    return (
      <div className="page-container">
        <ul className="product-list">
          {this.state.products.map(p => (
            <li className="product-list-item" key={p.id}>
              {p.name}
            </li>
          ))}
        </ul>
      </div>
    );
  }
}

and the ts compiler flagged an error saying: Property id does not exist on type 'never'

Why is that?

devak23
  • 419
  • 1
  • 4
  • 6

2 Answers2

1

The second form is class properties which is a stage 3 JavaScript proposal (meaning it's not part of the language yet). Adding properties on the constructor is the old ES2015 way (called maximally minimal classes).

In your case there is no functional difference.

TypeScript requires you to declare class fields for type safety - hence the warning.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
1

How to define the state for a React component is as subjective as the coding styles React promotes itself. Usually I go the very strict route which looks as follows:

type State = {
    someStateVar: number[];
};

export class MyComponent extends Component<{}, State> {
    public readonly state: State = {
        someStateVar: [],
    };

    public async componentDidMount() {
        // Dynamically fetch data (maybe via axios) and populate the new state
        const data = await axios.get<number[]>(...);
        this.setState({ someStateVar: data });
    }
}

As you can see, I explicitly mark state as readonly just make sure, nobody attempts to write directly to it (even though IDEs and linters can check for those errors without the precaution nowadays).

Another reason why I prefer to not set the state manually with an assigment is that it might encourage wrong handling of state. You are never to assign something directly to state in a class method without the use of setState.

Furthermore I am basically defining the defaults right away and populating the state with dynamic data in componentDidMount. This approach allows you to keep the code concise by dropping an explicit constructor definition as you should move such an initialization to componentDidMount anyways and use the constructor only for binding methods if you don't use the class member arrow notation for those in the first place.

Christian Ivicevic
  • 10,071
  • 7
  • 39
  • 74