codemix / babel-plugin-typecheck

Static and runtime type checking for JavaScript in the form of a Babel plugin.
MIT License
886 stars 44 forks source link

Create React propTypes from props annotations #125

Closed ntkoopman closed 8 years ago

ntkoopman commented 8 years ago

These changes add a propTypes object to a class based on the type annotations you specify. This transformation is only applied when:

Example:

export class Foo extends Component {

    props: {
        user: ?string;
    };

    render() {
        return <div>Hello, {this.props.user || "World"}!</div>;
    }

}

becomes

export class Foo extends Component {

    render() {
        return React.createElement(
            "div",
            null,
            "Hello, ",
            this.props.user || "World",
            "!"
        );
    }

}
Foo.propTypes = {
    user: function (props, name, component) {
        var prop = props[name];

        if (!(prop == null || typeof prop === 'string')) {
            return new Error("Invalid prop `" + name + "` supplied to `" + component + "`.\n\nExpected:\n" + "?string" + "\n\nGot:\n" + _inspect(prop) + "\n\n");
        }
    }
};

I've tested this on a small app and it works so far.

phpnode commented 8 years ago

@ntkoopman this is very nice, thanks! Because we're ultimately trying to reach compatibility with flow I wonder if we should also support the type-parameterized class definitions that Flow recommends for React classes, e.g.

type Props = { initialCount: number }
type DefaultProps = { initialCount: number }
type State = { count: number }

export class Counter extends React.Component<DefaultProps,Props,State> {
  constructor(props: Props, context: any) {
    super(props, context)
    this.state = {count: props.initialCount}
  }
  tick() {
    this.setState({count: this.state.count + 1})
  }
  render(): React.Element {
    return (
      <div onClick={this.tick.bind(this)}>
        Clicks: {this.state.count}
      </div>
    )
  }
}

(here I'm specifically talking about the class Counter extends React.Component<DefaultProps,Props,State> {} annotation)

ntkoopman commented 8 years ago

It probably wouldn't be to hard to support the case you mention, but as I understand it the parametrized version is meant more for higher order components. The official documentation even has

class HOC<Config> extends React.Component<void, Props<Config>, State<Config>> 

as the example.

phpnode commented 8 years ago

@ntkoopman I'm using flow a lot and the type-parameterized version seems to work pretty well, actually recently flow started rejecting react classes without it (I ran into this on a project the other day) so I'm not really sure what we should do given that the goal is to reach flow compatibility in the next major version.

ntkoopman commented 8 years ago

Are you sure? I just compiled the latest version from master, and I don't see any difference in behaviour from Flow 0.22.0 (as in, both versions work). But, I guess I could try to add support for the parameterized syntax.

ntkoopman commented 8 years ago

When updating the pull request I also ran into the issue of required type parameters altough the project I wrote this for is still humming along fine without them. Anyway, I've updated this pull request with support for parameterized React classes as well.

It didn't build because of #129

phpnode commented 8 years ago

@ntkoopman very nice, thanks for this, I added you as a repo collaborator.

phpnode commented 8 years ago

released in 3.8.0

taylorstine commented 7 years ago

Just to confirm though, in the current version (3.9.0) , when using class Counter extends React.Component<DefaultProps,Props,State> only the Props annotation is supported? The DefaultProps and State annotations do not have any effect, right?