Edit: last update on this: it appears only when 'action' is imported.
--
I don't know if I'm doing something wrong or if it's a bug, but it looks pretty straightforward:
import { action } from 'somewhere'
// action: (param1: string) => {| type: string, param1: string |}
const actions = {
action
}
type Props = typeof actions
class MyComponent extends Component<Props> {
componentDidMount() {
this.props.action(1, 2, 3) // No errors when I run 'flow check'!
}
}
Flow correctly infers the type of the function outside of the component. But, once it is passed to the component (this.props.action), it becomes any.
I also tried, without success:
type Actions = typeof actions
type Props = Actions
type Props = {} & Actions
type Props = { ...Actions }
type Props = {| ...Actions |}
Seems like flow is marking action as any. If you are importing action from a third-party module, in order to have flow mark it the correct type make sure you have the libdef for the module. Here is the link to the docs that provide instruction on how to that.
If you are importing action from a file within your project make sure that the file has flow initialized.
Maybe it was not clear, so here is another example with some comments:
// index.js
// @flow
// Let's import a function I've written in my project, with the '// @flow' annotation:
import { action } from '../state/actions'
// action: (param1: string) => {| type: string, param1: string |}
// This is the type inferred by Flow. I can see it in my editor.
// Now let's call it with some unexpected params:
action(1, 2, 3)
// Error 1: Cannot call `action` with `1` bound to `param1` because number [1] is incompatible with string [2]
// Error 2: Cannot call `action` because no more than 1 argument is expected by function [1]
// In this first case, Flow is catching all the errors as expected.
// Now let's pass 'action' to a component through its props:
const mapActionsToProps = {
action
}
type Actions = typeof mapActionsToProps
type Props = {|
...Actions,
otherProp: string,
|}
class MyComponent extends Component<Props> {
componentDidMount() {
this.props.action(1, 2, 3)
// In this case, no errors! Flow is not catching anything.
// Yet it's the exact same function, passed through the Props
}
}
connect(null, mapActionsToProps)(MyComponent)
So, either I'm not typing the Props correctly, either there is a bug somewhere.
I'd try doing:
type Props = {|
...$Exact<Actions>,
otherProp: string,
|}
@AlicanC Just tried and it doesn't fix it. Actually
const mapActionsToProps = {
action
}
type Actions = typeof mapActionsToProps
already defines Actions as an exact object.
So I just discovered that it's actually because action is imported.
I was trying to reproduce the bug on flow.org/try but it works as expected when it is in the same file: https://flow.org/try/#0JYWwDg9gTgLgBAJQKYEMDGMA0cDecDCE4EAdkifAL5wBmURcA5FKhowFDtqkDO86MYKTgBeOAAowKKChABGAFxw+UYCQDmASlEA+CXhgBPMEiWMAgvgAqASQDyAOUbYpM+XEqbOAoSXFzsACZsAGYvdiMTOAAFejAeUVwAHzgAOnTzDF8EpMpONAAbFB4EgDEICDgkAA8YcgATBMJiMgoAHliIeL0cdjg4bhbyGAARYHqAWQgAVwpxbV7+-pgAC2AeVLA4jZ9SfyDQr368vK5efizeRMW4XZIlh8en55el9lPIpDhMwSuxT4gNFulxIPCAA
I also confirm that the type of the imported action is correctly inferred out of the component's props:
import { action } from '../state/actions'
// action: (param1: string) => {| type: string, param1: string |}
const mapActionsToProps = {
action
}
type Actions = typeof mapActionsToProps
type Props = {| ...Actions |}
class MyComponent extends Component<Props> {
componentDidMount() {
action(1, 2, 3) // Error 1: Cannot call `action` with `1` bound to `param1` because number [1] is incompatible with string [2]
// Error 2: Cannot call `action` because no more than 1 argument is expected by function [1]
this.props.action(1, 2, 3) // No errors!
}
}
And if in the same file, it works as expected:
const action = (param1: string) => ({ type: 'ACTION', param1 })
// action: (param1: string) => {| type: string, param1: string |}
const mapActionsToProps = {
action
}
type Actions = typeof mapActionsToProps
type Props = {| ...Actions |}
class MyComponent extends Component<Props> {
componentDidMount() {
action(1, 2, 3) // Error 1: Cannot call `action` with `1` bound to `param1` because number [1] is incompatible with string [2]
// Error 2: Cannot call `action` because no more than 1 argument is expected by function [1]
this.props.action(1, 2, 3) // Error 1: Cannot call `action` with `1` bound to `param1` because number [1] is incompatible with string [2]
// Error 2: Cannot call `action` because no more than 1 argument is expected by function [1]
}
}
So there is a bug when combining import, typeof (?) and Component<Props>.
@ggregoire have you found a workaround for this or what did you end up doing?
@kangax No I didn't find any workaround. I write the type of every action that I pass to my components…
type Props = {|
action: (param1: string) => {| type: string, param1: string |},
action2: () => void,
// and so on…
|}
class MyComponent extends Component<Props> { ... }
@ggregoire I'm seeing a similar issue where props lose their types:
The shape seems to be determined properly:

But the 2nd level props are any:

I also checked with type-at-pos and it's consistent with what I'm seeing in VSCode:
➜ git:(master) ✗ flow type-at-pos app/scripts/xxx.jsx 10 27
{byUuid: {[string]: Array<Ticket>}, error: mixed, loaded: boolean, loading: boolean}
➜ git:(master) ✗ flow type-at-pos app/scripts/xxx.jsx 10 37
any
It's pretty bizarre how Flow recognizes this shape correctly but then reports property's value in the same expression as any. Perhaps some kind of internal shape caching?
@mroch any hints on what this could be?
@kangax I don't know if this is related to my issue. In your screenshot, zendeskTickets is not a "prop", it's just a nested object inside another object that you manually typed (state: GlobalState).
If you remove ...ReduxProps from Props, is state.zendeskTickets.loader still inferred as any? If yes then it's definitely not related to my issue.
@ggregoire I felt it might be the same issue because:
connect, loaded is determined correctlytype Props = ReduxProps) loaded is determined correctlyIn my case, however, importing GlobalState or defining it locally doesn't make a difference.
I think that this bug is similar to issues https://github.com/facebook/flow/issues/7071 and https://github.com/facebook/flow/issues/4312
Most helpful comment
So I just discovered that it's actually because
actionis imported.I was trying to reproduce the bug on flow.org/try but it works as expected when it is in the same file: https://flow.org/try/#0JYWwDg9gTgLgBAJQKYEMDGMA0cDecDCE4EAdkifAL5wBmURcA5FKhowFDtqkDO86MYKTgBeOAAowKKChABGAFxw+UYCQDmASlEA+CXhgBPMEiWMAgvgAqASQDyAOUbYpM+XEqbOAoSXFzsACZsAGYvdiMTOAAFejAeUVwAHzgAOnTzDF8EpMpONAAbFB4EgDEICDgkAA8YcgATBMJiMgoAHliIeL0cdjg4bhbyGAARYHqAWQgAVwpxbV7+-pgAC2AeVLA4jZ9SfyDQr368vK5efizeRMW4XZIlh8en55el9lPIpDhMwSuxT4gNFulxIPCAA
I also confirm that the type of the imported
actionis correctly inferred out of the component's props:And if in the same file, it works as expected:
So there is a bug when combining
import,typeof(?) andComponent<Props>.