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

Saving type as a property #46

Closed Radivarig closed 9 years ago

Radivarig commented 9 years ago

I'm using React and would like to define a type for state.

here is how I define it:

, getInitialState() {
    type stateType = {
      someStateVar: number;
    }
    this.stateType = stateType    //saving type for mixin

    return this.typedState({
      someStateVar: 3
    })
  }

and this is my state mixin:

var StateTypeMixin = {
  handleSetState(s) {
    this.setState(this.typedState(s))
  }
, typedState(s) {
    var newState = Object.assign({}, this.state, s)
    var stateType = this.stateType    //getting state
    var typedState : stateType = newState
    return typedState
  }
}

It works when I copy type stateType = .. to typedState mixin function where it is used.

Otherwise I get the following error:

Uncaught TypeError: Value of variable "typedState" violates contract, expected stateType got Object

Even tho in console.log(stateType) it shows what seems to be the same function from both places

stateType(input) {
    return input != null && (typeof input === 'undefined' ? 'undefined' : _typeof(input)) === 'object' && typeof input.someStateVar === 'number';
}

and the stateType(newState) returns true.

Same happens when I pass the type as argument:

, getInitialState() {
    type stateType = {
      someStateVar: number;
    }
    return this.typedState({
      someStateVar: 3
    }, stateType)

Any ideas? Thanks

phpnode commented 9 years ago

types are not values so this won't work, you can't pass them around in this way because they do not exist when the program is running.

Radivarig commented 9 years ago

Is it bound to this plugin, or is it a feature request I can post on Flow? I'd just need to be able to recreate the type on the other end, I see that it is a generated function.

Radivarig commented 9 years ago

It is possible to export and import types http://flowtype.org/blog/2015/02/18/Import-Types.html

phpnode commented 9 years ago

you can import and export types but you cannot use them as anything other than types, so we support this:

foo.js

export type User = {
  name: string;
};

bar.js

import type {User} from "./foo";

function userName (input: User): string {
  return input.name;
}

userName({name: 'Radivarig'});

But not this:

import type {User} from './foo';

console.log(User);
Radivarig commented 9 years ago

This is my workaround:

var StateTypeMixin = {
  handleSetState(s) {
    this.setState(this.typedState(s))
  }
, typedState(s) {
    var newState = Object.assign({}, this.state, s)
    if (this.stateType(newState)) return newState
    else throw 'Type mismatch for state: ' +JSON.stringify(newState)
  }
}
phpnode commented 9 years ago

@Radivarig I think your approach here is a mistake and should reconsider, this is not how either flow nor this plugin is supposed to be used and your code will stop working when you turn the plugin off.

If you describe what you're trying to achieve, maybe someone can describe a more appropriate solution.

Radivarig commented 9 years ago

I want to define a type for state, so if I setState with a wrong type I'd like to throw an error, like in the workaround above. So far it works as I intended, except I don't get warned for the exact property but the whole state (because I assign new state change to current state) but I can backtrace where it was called and locate the bug. If I'll be removing the plugin I can alter handleSetState to just call setState. Any suggestions on how to do it more properly are most welcome. Thanks

phpnode commented 9 years ago

Something like this?

type ValidAddress = {
  address: string;
  city: string;
  countryId: number;
};

export default class Address extends React.Component {
  getInitialState (): ValidAddress {
    return {
      address: "123 Fake Street",
      city: "London",
      countryId: 123    
    };
  }

  setState (state: ValidAddress) {
    return super.setState(state);
  }

  getState (): ValidAddress {
    return super.getState();
  }
}
Radivarig commented 9 years ago

Overrides will do the trick! Thanks

This is how I used it for non-class es6 synthax:

// outside of the class
type stateType = ...

var App = React.createClass({
  getInitialState() {
    var s: stateType = { ... }
    return s
  }
  , componentDidMount() {
    var setState = this.setState.bind(this)
    this.setState = function(s: stateType) {
      setState(s)
    }
  }
// ...