(forked off of #267)
I'd like to use Flow types to document my React Component's props. If it were possible to do this, then React.PropTypes would be entirely superfluous鈥攊t's a less precise way of documenting the types, and the checks are performed at runtime instead of compile time.
This would allow lots of duplicated type definitions to be removed. You can see this in the Flow example code. Looking at just the Message type, for example, its definition is duplicated four times in the six components:
(as Flow type)
https://github.com/facebook/flow/blob/master/examples/flux-chat/js/components/ThreadSection.react.js
https://github.com/facebook/flow/blob/master/examples/flux-chat/js/components/MessageSection.react.js
(as React type)
https://github.com/facebook/flow/blob/master/examples/flux-chat/js/components/ThreadListItem.react.js
https://github.com/facebook/flow/blob/master/examples/flux-chat/js/components/MessageListItem.react.js
Ideally you'd be able to write something like:
/* @flow */
type Message = {
authorName: string;
date: Date;
text: string;
};
var MessageListItem = React.createClass({
propTypes: {
message: Message
},
render: function() {}
});
Flow could use the type information for type checking, then strip it out completely for production, i.e. translate this to:
var MessageListItem = React.createClass({
render: function() {}
});
This is supposed to become easier with the introduction of class-based React components. An alternate class-based syntax:
type Props = { /*... */ }
type State = { /* ... */ }
class MyComponent extends ReactComponent<Props, State> {
render() {
/* typeof this.props is Props */
/* typeof this.state is State */
}
}
My understanding is that this is also blocked by the lack of bounded generics, so the the types of getDefaultProps, getDefaultState, setState, and setProps can be specified.
The plan is to be able to annotate the props type using Flow. It is made easier by ES6 classes since we have a nicer syntactic space for it.
class MyComponent extends React.Component {
props : Props;
state : State = { initial: 'value' };
render() {
/* typeof this.props is Props */
/* typeof this.state is State */
}
}
The syntactic solution for React.createClass is kind of messy.
var MessageListItem = React.createClass({
props: (null : ?Props),
render: function() {
if (this.props !== null) { // guard for safety
/* this.props is Props */
}
}
});
Perhaps Flow could build some special support that allows annotating props in this way but it seems like more work than it is worth, since the world is moving on to ES6 classes.
The goal is to provide a "React Modern" package which doesn't include runtime features for React.createClass/PropTypes/createElement and instead relies on language features like ES6 Classes/Flow/JSX. The other stuff will be continued to be supported in "React Classic".
If your entire codebase is Flowified, you don't need dynamic type checks.
However, PropTypes still serve the dynamic type checking scenario in the boundary to un-Flowified code. E.g. if you publish it online, others might want your type annotations for runtime type checking. However, I think that would be better handled by a generic Flow Safe or Flow Dynamic Boundaries solution rather than something React specific. Once we have that, there is no need for React PropTypes anymore.
I'll note that it's currently not possible to use React.Component classes in JSX with flow because React.createClass is currently special cased as a CustomClassT, and the only way to generate that type is with React.createClass.
EDIT: For anyone looking at this now, the above is no longer true. React.Component works great.
@danvk, you should be able to use flow type syntax with React.Component subclasses now. If that is posing any issues for you, please reopen this. Thanks for the report!
@samwgoldman can you point to an example of this? Would be much appreciated!
@yanivtal There are a bunch of examples in the tests for the new React API. Note that many of the examples are designed to fail in an expected way, but that should give you an idea.
Was the hope to be able to do away with prop types and do type annotations with flow? All the examples use React.createClass. I'm hoping to be able to use ES6 classes, specify prop types with flow and then get autocomplete through https://atom.io/packages/ide-flow. Is something like that possible today?
Ahh, I found classes.js. Looks clean.
I don't seem to be able to make it work (running Flow 0.14.0). Here's my test case:
/* @flow */
type P = { width: number; };
class Label extends React.Component {
props: P;
render() {
return <div style={{width: this.props.width * 2}}>Label</div>;
}
};
var f = <Label width="100px" height="20px" />;
I expect this to produce 2 errors in the last line: that width is expected to be a number, and that height is not supported. Instead I get "No errors!" message. Am I doing anything wrong? I also tried extending React.Component<any,P,any> -- with the same result.
@pasha-mf You need to specify the type of defaultProps as well (ref #398).
Also, passing too many props is not an error, so Flow won't complain about it.
/* @flow */
var React = require("react");
type P = { width: number; };
class Label extends React.Component {
props: P;
static defaultProps: {}; // this part is important
render() {
return <div style={{width: this.props.width * 2}}>Label</div>;
}
};
var f = <Label width="100px" height="20px" />;
Ok, but even after adding the declaration for defaultProps it still doesn't complain about width being a string...
Are you sure? I just tried and got this:
test.js|14 col 9 error| React element: Label
|| Error:
test.js|14 col 22 error| string
|| This type is incompatible with
test.js|4 col 19 error| number
||
(it's working now)
Does this work with ES6 import? I have
/* @flow */
import React from 'react';
type P = { width: number; };
class Label extends React.Component {
props: P;
static defaultProps: {}; // this part is important
render() {
return <div style={{width: this.props.width * 2}}>Label</div>;
}
}
let f = <Label width="100px" height="20px" />;
Using Flow 0.17.0 and I get No Errors.
@pasha-mf Could you shed some light on anything you did differently to get it working? I pasted your code verbatim but I get zero errors. I also had a quick discussion with @samwgoldman with my own test case (pasted below) that I just tried on an empty folder which he could also get to display the correct errors whereas I still get 0 so if anyone has experienced this and figured it out please let me know! (I'm testing with 0.18.1 but just in case I tried the last few versions as well and couldn't get anything)
My own simple case:
/* @flow */
import React from 'react';
type P = {
prop1: string;
prop2: number;
};
class Test extends React.Component {
static defaultProps: {};
props: P;
render() {
return <div>
{this.props.prop1 * this.props.prop2} // get expected error for incompatible types
</div>;
}
}
class ParentTest extends React.Component {
render() {
return <Test prop1={1} />; // hoping to get error for wrong type and missing prop
}
}
@frankychung @samwgoldman I'm also using 0.18.1 and do not get any flow warnings/errors with the example code.
However after a bit of experimentation I was able to get the correct flow error if I extend ReactComponent instead of React.Component
ie
class Label extends ReactComponent { ... }
While this type checks correctly it does not run in the browser as ReactComponent is a flow declaration and not a real class
@frankychung @cloudkite Isn't this bizarre? I just tested Franky's piece: create a new directory, do flow init in that directory, create file test.js (has to have extension .js) and paste the code into that file, and finally run flow. It gives me 3 errors, as expected (1 error in line 16 and 2 in line 23).
My Flow version is also 0.18.1 (more specifically, revision d3f6f17e07cff4afef9b48eb0b21f3557ff3cdce). I did a clean make after the pull, could this be the reason?
I really have no idea... When I wrote the comment 2 months ago I believe what made the difference was to switch from React.Component to ReactComponent. But right now both versions work fine for me, so it could have been just a coincidence.
Sorry I can't be of much help here...
Great, using ReactComponent gives me the expected errors! @pasha-mf: Thanks for checking! I tried building from latest master but no errors as well. As @cloudkite mentioned, using ReactComponent doesn't work in the browser. Could you share how you were able to switch to it?
@frankychung @samwgoldman did a bit more digging and managed to get it working if I explicitly cast
class Label extends (React.Component : typeof ReactComponent) { ... }
Which seemed strange since given
https://github.com/facebook/flow/blob/master/lib/react.js#L177
This got me thinking that maybe flow isn't picking up the builtin declaration of react properly.
so I changed my .flowconfig to
[ignore]
.*/node_modules/react/.*
Which now gives the correct flow warnings without the explicit cast :+1: :fireworks:
I thought declarations took preference over node_modules?
Ah, glad you figured out the solution (adding an ignore where a declaration exists). This issue is tracked in #676.
@cloudkite Thanks for looking into it! That solved my problem too. Even in my simple test cases I had installed react into my node_modules which explains why some of us were seeing different things. :+1:
My favorite thing about types is that they reduce cognitive load for developers. Runtime reflection is really nice for developers because the objects can literally explain themselves to you.
But another great thing about runtime type reflection is that you can build tools into the application itself that help developers extend the application. The code is data that is used by the app as data at runtime.
See existing systems like Unreal Engine in-game blueprint editing for an example, and read my Sitepoint article, The Future of Programming.
PropTypes provides a pretty nice interface that would allow those features to be developed. As it is today, Flow doesn't. Of course, we may be able to fix that with the future development of the JS Reflect API, but that's a long way off.
These are the reasons we're working on rtype & rfx.
I'd be happy to support flow-style annotations in rfx if anybody else is interested in using it.
rfx already supports PropTypes style predicate functions, so it could be a viable solution for React users in a very short time with a little TLC.
@frankychung thanks for sharing that snippet above. I hadn't realized you could declare props and state properties on your React.Components to get type checking. This is fantastic!
How do ReactElement and ReactComponent get on scope for type_aliases.js in the tests?
type _ReactElement<D,P,S,C:ReactComponent<D,P,S>> = ReactElement<D,P,S>;
type $jsx<C> = _ReactElement<*,*,*,C>;
@ericelliott In terms of design space, both PropTypes (by virtue of being development only checks) and Flow types have very intentionally left type annotations out of the semantic space due to what leaving it out buys us. They're not just lacking semantic runtime meaning as a missing feature but as an intentional added feature. Unlike say, Angular with their use of annotations for dependency injection purposes.
It would make your case better if you explain why you don't need the features you get from leaving runtime semantics out (e.g. perf and ability to freely iterate on the inferred type system without risking runtime bugs). Rather than explaining why having them would buy you new features. Those are kind of obvious. The trade-off is less obvious.
both PropTypes (by virtue of being development only checks) and Flow types have very intentionally left type annotations out of the semantic space
Isn't this true only in the production compile? AFAIK, Flow provides no runtime analysis or reflection at all, whereas, PropTypes do in development.
I agree that it's great to be able to turn off type checking in production mode, but I maintain that the ability to opt into runtime reflection and type checking is an important feature, and I wouldn't want to see that feature abandoned.
Note that we've had many requests to strip out PropTypes from production code since they add significant start up time and file weight for no little use since type checks are disabled. That might be something React could/would do.
Note that we've had many requests to strip out PropTypes from production code since they add significant start up time and file weight for no little use since type checks are disabled. That might be something React could/would do.
Absolutely agreed on that point, but couldn't that also be done with a compiler option?
The way I see it, type checking and React are two different things, each with their own sets of concerns. Is type checking useful for React components? Absolutely -- as it is for _every other kind of code._
Should React marry itself to any particular kind of type checking? _Probably not._ Not all React users want to be Flow users. Some React users may prefer TypeScript, or rtype, or JSDoc + closure compiler.
Maybe PropTypes is just one of _many available options._ Right now it has baked in support. Maybe if you decide you want to move away from that, there should be documentation about how to roll your own support for it. Perhaps the existing support could be extracted as a separate module that's easy for the community to use, or not use, as they like.
I'm all for type systems with compiler support to strip them out, or include them for runtime capabilities, at the option of the user.
Re: stripping PropTypes in production, there's babel-plugin-transform-react-remove-prop-types.
As for using Flow annotations as runtime information (and vice-versa), I've built json-to-flow to handle converting JSON schemata into Flow types, and @seanhess's runtime-types handles converting Flow types back into JSON.
You can't currently round-trim them due to output differences but if there is demand I'll bring them in line.
I wonder if something like traits would solve this. https://github.com/facebook/flow/issues/2135
@STRML there's also this for runtime flow checking: https://github.com/gcanti/babel-plugin-tcomb
Awesome link @jedwards1211!
I still have some errors with flow. I'm currently using functional-component and type checking does not effect prop uses. In the component, i get error but when i try to use my component and pass wrong types, it doesn't give me any error. How can i fix it?
I still have some errors with flow. I'm currently using functional-component and type checking does not effect prop uses. In the component, i get error but when i try to use my component and pass wrong types, it doesn't give me any error. How can i fix it?
Make a new issue with a reproduction of your issue on https://flow.org/try/
Most helpful comment
@STRML there's also this for runtime flow checking: https://github.com/gcanti/babel-plugin-tcomb