Hello. First off, thanks for Flow :)
I have a component that basically looks like this:
export default class Root extends Component {
constructor(props : Object) {
super(props);
this.state = {};
this._handleAppStateChange = this._handleAppStateChange.bind(this);
this.interval = null;
}
_handleAppStateChange() {
const { refreshToken } = this.props;
if (this.props.auth.token.current) {
refreshToken();
}
}
}
And I receive the error
property '_handleAppStateChange' Property not found in Root.
If I put the constructor at the bottom, flow doesn't care, but eslint does. Any help with best practices here is appreciated.
Thanks for opening this issue. We really need to document this behavior more clearly, but let me give a high-level overview here.
Methods on classes (and interfaces) are considered to be read only by Flow. This trade-off makes covariance possible for methods, which is hugely important. If we did allow methods to be read/write, covariance would be unsound鈥攚e'd make them invariant鈥攁nd other very common patterns would be type errors.
We're very aware of this autobinding pattern in React components. One workaround is to do something like this in the constructor:
(this:any).myMethod = this.myMethod.bind(this);
We are looking into ways to make this pattern possible while retaining covariance for methods.
Ah okay, that makes a lot of sense. Thanks! I'll leave it up to you to close this issue.
If you're using the Babel stage-1 preset and have the following in your .flowconfig:
[options]
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
then you can do this:
export default class Root extends Component {
constructor(props : Object) {
super(props);
this.state = {};
this.interval = null;
}
_handleAppStateChange: ()=>void = () => {
const { refreshToken } = this.props;
if (this.props.auth.token.current) {
refreshToken();
}
};
}
It's a little verbose in that you have to specify the type of _handleAppStateChange
. If it had any arguments, then it will start to look pretty redundant.
Well, you can also go one step further and move more things out of the constructor as long as they don't depend on arguments to the constructor:
export default class Root extends Component {
state: Object = {};
interval: any = null;
_handleAppStateChange: ()=>void = () => {
const { refreshToken } = this.props;
if (this.props.auth.token.current) {
refreshToken();
}
};
}
I've never seen an instance method declared that way. What is this about?
_handleAppStateChange: ()=>void = () => {
It's the proposed ES7 syntax for a property initializer (with a Flow type declaration mixed into it). Compare it to how state
and interval
are initialized in my second example. _handleAppStateChange
is being initialized to an arrow function. When you use an arrow function in a property initializer, this
is bound to the instance of the class.
Note that the syntax isn't set in stone, so it might work differently in future Babel and Flow versions!
(this:any).myMethod = this.myMethod.bind(this);
gives me the following error:
src/components/FundListGroup/FundListGroup.js:19
19: (this:any).renderHeaderCell = this.renderHeaderCell.bind(this)
^ Unexpected token :
As a workaround I introduced a self
variable like so:
const self: any = this
self.renderHeaderCell = this.renderHeaderCell.bind(this)
This seems to work.
Hello there,
I'm starting a new React Native and React project and this time I'm determined to use Flow.
Today I played a little with Flow and it seems that binding method to the class with arrow function (ES7) won't work (and it is the way I always used in my previous projects):
export default class App extends React.Component {
_test = (): void => {
console.log('test')
}
render (): React.Element { // Flow error here: Unexpected identifier
return <HomeScreen />
}
}
.babelrc:
{
"presets": ["react-native"],
"plugins": ["transform-decorators-legacy", "syntax-flow"]
}
It seems that the alternatives are:
1. Biding in the constructor with (this:any)
(Worst option for me, I tend to have a lot of binded method and this seems to be the most verbose way)
2. As suggested by @AgentME :_handleAppStateChange: ()=>void = () => {
... a bit weird?
3. Using the @autobind decorator, my favorite option at the moment, even if I get the obnoxious warning from Flow: Experimental decorator usage. Decorators are an early stage proposal that may change
Any suggestions/options/alternative?
Thank you in advance and also thank you for all the hard work you put in Flow, have a nice day!
You don't have the syntax correct here:
_test = (): void => {
_test
is missing a type annotation , and I'm not sure that the : void
part is a supported way to show the return types of arrow functions. The right syntax is
_test: ()=>void = () => {
Oh, you can specify the return types of arrow functions like that. I think there might be a parsing issue with Flow there.
Nevermind, class properties do work, but need an ending semicolon.
Any reason for the needed semicolon?
Babel doesn't need it and it has already been discussed in the past and unfortunately clashes with StandardJS and other linters...
Both a)
and b)
works fine for when you do not have a parameter.
_test: ()=>void = () => {}
_test = (): void => {}
But when you do, as in b')
, my autocomplete within Nuclide based on Flow fails to name the parameter correctly.
_test: (myText: string)=>void = (myText: string) => {}
_test = (myText: string): void => {}
I've tried writing code like a')
, but my souls is being torn apart...
@consideRatio did you try adding an ending semicolon? it works that way for me, take a look at #1682
The problem with () => {} solution is that those are all anonymous functions, so when you're debugging you'll see a big stack trace of anonymous functions and it would be hard for you to work like that. Also, if you're doing any sort of profiling you'll have the same problem, you'll see a sea of anonymous functions, and you would have to dive into the code to figure out which function are you looking at.
To solve that issue you could write:
someMethodName = someMethodName(arg1, arg2, arg3) => { ... }
There's a bit repetition there too.
Is there any update on handling this syntax in a constructor: this.fn = this.fn.bind(this);
?
FYI on Flow ^0.36.0 the following syntax works fine:
export class PlaceList extends Component {
props: {
places: Array<Place>,
isLoading: boolean,
fetchPlaces: () => void,
navigator: Object
}
static route = {
navigationBar: {
title: 'Home'
}
}
componentDidMount () {
this._loadList()
}
_loadList = () => {
this.props.fetchPlaces()
}
_handlePlacePress = (placeId: string) => {
const { navigator } = this.props
navigator.push('test')
}
render () {
return (
<View style={styles.container}>
...
</View>
)
}
}
Even without the semicolons 馃帀 !
@Gpx it's this: any
not this:any
. You need the space or you'll see the error you're seeing. Not sure why Flow does this, but that's how it is.
https://medium.com/@john1jan/react-binding-revealed-aa458df8c136#.fd5z0vmjl
Still not working?
The Arrow Syntax is no working alternative since it breaks hot loading.
If anyone happens to run across this.. You can at least make the syntax look a little bit nicer by implementing an interface. Since some of these things with Flow can be maddening I figured I would write up a full overview so it can save others some time hopefully!
I ran into a situation where referencing an instance that is passed through various functions would showup as not typed by Flow:
setState is currently something like this as a method on my class (use of Object.assign here because state is a covariant property on the instance):
setState = (state: $Shape<Datastream$ClientState>) => {
Object.assign(this.state, state);
return this;
};
Where as an example our state can be
export type Dash$Identities = {
+dealerID: string,
+projectID?: string,
+systemID: string,
};
export type Datastream$ClientState = {
authToken: ?string,
identities: ?Dash$Identities,
};
This can be a pretty big deal - for example -- I am using this in a back-end api where I do various validation of data structures from REST Responses, etc. Not to mention, coverage being off even a little bit can drastically reduce the quality of Flow's checking.
Once I validate them - flow knows the structures -- so if I do something like the setIdentities or setState on the instance - I want it to be able to validate against that structure vs what it expects... while there are no errors in the current implementation without changes, the checking simply isn't there.
For example, its not gonna care if I do (other than the coverage warning):
So after reading this post I found that adding the redundant type right before the definition does work:
setState: (state: $Shape<Datastream$ClientState>) => DatastreamClient = (
state: $Shape<Datastream$ClientState>,
) => {
Object.assign(this.state, state);
return this;
};
Awesome - but ugly... I'm definitely not going to do that ugliness everywhere.... enter interface
and implements
! Still typing it ttwice - but now it is at least with my type files that are imported from the type files rather than in a place that has zero value:
export interface Datastream$Client {
+identity: Datastream$ClientID;
+state: Datastream$ClientState;
setState: (state: $Shape<Datastream$ClientState>) => DatastreamClient;
setIdentities: (identities: $Dash$Identities) => DatastreamClient;
}
and when defining the class (after importing the types):
class DatastreamClient implements Datastream$Client {
// other methods and such...
setState = (state: $Shape<Datastream$ClientState>) => {
Object.assign(this.state, state);
return this;
};
setIdentities = (identities: Dash$Identities) => {
this.state.identities = identities
// continue parsing identities...
console.log(identities);
return this;
};
}
Success! Both type validation and flow coverage achieved.
Anyway -- hope this little writeup helps some of you out! Would love to see more examples like this showing various ways to implement things with Flow myself... been extremely stressful trying to deduce Flow's inference when it comes to inheritance and such!
@bradennapier aren't those type parameters redundant when using an interface?
I think this thread has gotten a bit off topic, the original request was related to best practices on how to bind in the constructor, not how to avoid using bind altogether.
So what is suggested?
(this:any).myMethod = this.myMethod.bind(this);
or statically attaching something like
myMethod: Function;
or something else entirely?
There is performance/memory issues w/ using arrow functions over using bind, may be trivial or considered a pre-optimization but the decision shouldn't be influenced based on using Flow.
Most helpful comment
Thanks for opening this issue. We really need to document this behavior more clearly, but let me give a high-level overview here.
Methods on classes (and interfaces) are considered to be read only by Flow. This trade-off makes covariance possible for methods, which is hugely important. If we did allow methods to be read/write, covariance would be unsound鈥攚e'd make them invariant鈥攁nd other very common patterns would be type errors.
We're very aware of this autobinding pattern in React components. One workaround is to do something like this in the constructor:
We are looking into ways to make this pattern possible while retaining covariance for methods.