tc39 / proposal-class-public-fields

Stage 2 proposal for public class fields in ECMAScript
https://tc39.github.io/proposal-class-public-fields/
487 stars 25 forks source link

calling subclass property initializers in the constructor of a superclass #28

Closed jorgenhorstink closed 8 years ago

jorgenhorstink commented 8 years ago

I'm working on a React framework suitable for my projects. It would be great if I could set this.state = this.getState(); on the constructor of the Page superclass, but it does not seem to work. Is this intended behaviour of the property initializers proposal, or am I missing something?

export default class BankTransactionPage extends Page {

    constructor(props) {
        super(props);
    }

    getState = () => {
        return {
            bankTransaction : BankTransactionRepository.getById(this.props.bankTransactionId)
        };
    }

    componentWillLoad = (props) => {
        const bankTransactionId = props.params.bankTransactionId;

        Command.execute(new LoadBankTransactionPageCommand(bankTransactionId));
    }

    // ...
}

export default class Page extends React.Component {
    constructor(props) {
        super(props);

        // Bummer, this.getState seems undefined...
        this.state = this.getState();
    }

    componentDidMount() {
        this.componentWillLoad(this.props);

        Command.addListener(this);
    }

    componentWillReceiveProps(props) {
        this.componentWillLoad(props);
    }

    componentWillUnmount() {
        Command.removeListener(this);
    }

    processEvent = (event) => {
        this.setState(this.getState());
    }
}
ljharb commented 8 years ago

Property initializers are run after the super call, so no, you wouldn't be able to access them like that.

wmertens commented 8 years ago

@jorgenhorstink IMHO it is weird to have the parent class calling child class methods in the initialization (BankTransactionPage subclasses Page but Page calls BankTransactionPage's getState). The other way round, ok.

Were you hoping that the getState is on the prototype prior to being set on the instance?

jeffmo commented 8 years ago

This example should work and should not give undefined.

Initializers are set after the super() call, but in your case you're executing this.state = this.getState() after all super() calls have been executed -- so I don't see why this.state would get initialized to undefined here.

EDIT: Oh I'm sorry, I observed your inheritance chain backwards. Looks like @ljharb is right -- the getState field would not have been set by the time you're trying to use it.

Consider this desugared code and it might become more clear why:

export default class BankTransactionPage extends Page {

    constructor(props) {
        super(props);
        this.getState = () => { // <-- DESUGARED FIELD INITIALIZER
            return {
                bankTransaction : BankTransactionRepository.getById(this.props.bankTransactionId)
            };
        };
    }

    componentWillLoad = (props) => {
        const bankTransactionId = props.params.bankTransactionId;

        Command.execute(new LoadBankTransactionPageCommand(bankTransactionId));
    }

    // ...
}

export default class Page extends React.Component {
    constructor(props) {
        super(props);

        // Bummer, this.getState seems undefined...
        this.state = this.getState();
    }

    componentDidMount() {
        this.componentWillLoad(this.props);

        Command.addListener(this);
    }

    componentWillReceiveProps(props) {
        this.componentWillLoad(props);
    }

    componentWillUnmount() {
        Command.removeListener(this);
    }

    processEvent = (event) => {
        this.setState(this.getState());
    }
}