0

I have this kind of initialisation going on in my React components:

export default class LoginForm extends Component {
    state = {      // (*)
        flash: {
            message: null,
            style: null
        } // initialiser for flash message: show nothing
    }
    showError(error_message: string) {
        this.setState({
                flash: {
                    message: error_message,
                    style: "danger"
                })
        }

Unfortunately, flow is treating the initialisation of the flash property of the state object as a type declaration, and in the subsequent setState() it flags the new declaration of the value of the flash property as a type mismatch ("string is incompatible with null").

How can I tell flow what is actually going on here, and thus avoid it reporting an error?


(*) note: I originally erroneously had a : in this line instead of = ... @DanPrince corrected that.

GreenAsJade
  • 14,459
  • 11
  • 63
  • 98

2 Answers2

2

Did you mean to use the class properties syntax instead?

export default class LoginForm extends Component {
  state = {
    flash: { message: null, style: null }
  }
}

As far as I'm aware, specifying class properties with : isn't and hasn't ever been a valid syntax. In this case, I would say that it's the expected behaviour for Flow to treat it as a type declaration.

If you want to create a class property and give it a type signature, you'd need to combine the two syntaxes.

class LoginForm extends Component {
  state
    : { flash: { message: ?string, style: ?Object } }
    = { flash: { message: null, style: null } };
}

Or in the generic case:

class {
  property:Type = Value;
}
GreenAsJade
  • 14,459
  • 11
  • 63
  • 98
Dan Prince
  • 29,491
  • 13
  • 89
  • 120
  • That is exactly what I meant ( `=` not `:`). But curiously, this does not solve the problem. Either way, flow complains about the mismatch between string and null! Come to think of it, it is not at all clear to me why flow is making the connection at all: I have no idea how it knows that the argument to `setState()` is the same object that is being set in `state = ...` – GreenAsJade Feb 26 '17 at 08:20
0

The type for React.Component can be parameterized with the prop types and state type.

type LoginProps = {
    // props here
}
type LoginState = {
    flash: {
        message: ?string,
        style: ?string
    }
}

export default class LoginForm extends Component<LoginProps, LoginProps, LoginState> {
    state : LoginState = { flash: { message: null, style: null } }
    showError(error_message: string) {
        this.setState({
                flash: {
                    message: error_message,
                    style: "danger"
                }
        })
    }
}

This should help Flow reconcile all the types correctly.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • Fascinating! I didn't know about `getInitialState`. What do you make of [this answer](http://stackoverflow.com/a/30668609/554807), which seems to advise against using getInitialState with ES6 classes? – GreenAsJade Feb 28 '17 at 00:36
  • I usually don't use ES6 classes with React, so I don't know if there are any pitfalls with `getInitialState()`. It's definitely plausible though. – Peter Hall Feb 28 '17 at 00:57