I'm having an issue with using disjoint unions directly as state for a react component. This does not typecheck correctly:
type AppState = LoggedInState | NotLoggedInState;
type LoggedInState = {mode: "loggedIn"; username: string};
type NotLoggedInState = {mode: "notLoggedIn"};
class App extends Component<any, any, AppState> {
state: AppState;
constructor(props: any) {
super(props);
this.state = {mode: "notLoggedIn"};
}
setLoggedIn() {
this.setState({mode: "loggedIn", username: "test"});
}
render() {
if (this.state.mode === 'notLoggedIn') {
return <Login loginFunc={this.setLoggedIn()}/>
} else {
return <Dashboard/>
}
}
}
The errors I get are:
src/Components/App.js:19
19: this.setState({mode: "loggedIn", username: "test"});
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call of method `setState`
19: this.setState({mode: "loggedIn", username: "test"});
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ property `username` of object literal. Property not found in
12: class App extends Component<any, any, AppState> {
^^^^^^^^ object type
src/Components/App.js:19
19: this.setState({mode: "loggedIn", username: "test"});
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call of method `setState`
19: this.setState({mode: "loggedIn", username: "test"});
^^^^^^^^^^ string. Expected string literal `notLoggedIn`, got `loggedIn` instead
8: type NotLoggedInState = {mode: "notLoggedIn"};
^^^^^^^^^^^^^ string literal `notLoggedIn`
Found 2 errors
However if I encapsulate AppState into an intermediate object:
type State = LoggedInState | NotLoggedInState;
type LoggedInState = {mode: "loggedIn"; username: string};
type NotLoggedInState = {mode: "notLoggedIn"};
type AppState = {state: State}
class App extends Component<any, any, AppState> {
state: AppState;
constructor(props: any) {
super(props);
this.state = {state: {mode: "notLoggedIn"}};
}
setLoggedIn() {
this.setState({state: {mode: "loggedIn", username: "test"}});
}
render() {
if (this.state.mode === 'notLoggedIn') {
return <Login loginFunc={this.setLoggedIn}/>
} else {
return <Dashboard/>
}
}
}
It typechecks correctly with no errors.
I'm having trouble figuring out if this is a bug in Flow or a bug in React (possibly related to how type parameters are used in its code?) however adding this issue is probably a good starting point.
I'm also experiencing this and wondering the same.
The possible bug here is that $Shape doesn't work on disjoint unions.
This is actually expected, because setState mutates the state and you can't mutate union type
Simple example:
type A = { t: 'A' };
type B = { t: 'B' };
type C = A | B;
var a: A = { t: 'A' };
var c: C = a;
c.t = 'B'; // can't do that
Why does it work if I'm modifying an encapsulating object? Additionally, I'm not setting mode directly - rather, I'm replacing the object.
@vkurchatkin Shouldn't the error be on the var c: C = a line. The objects are not marked covariant.
No, it shouldn't. X is always a subtype of X | Y, whatever Y is.
Most helpful comment
Simple example: