Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Special-case React StyleSheets #362

Closed
ide opened this issue Apr 4, 2015 · 10 comments
Closed

Special-case React StyleSheets #362

ide opened this issue Apr 4, 2015 · 10 comments

Comments

@ide
Copy link

ide commented Apr 4, 2015

Flow seems not to know that the output type of StyleSheet({k1: {...}, k2: {...}}) is an object with keys "k1" and "k2". Here is a simple repro case:

/**
 * @providesModule ExampleStyles
 * @flow
 */
var {StyleSheet} = require('react-native');
var ExampleStyles = StyleSheet.create({
  button: {},
});
module.exports = ExampleStyles;

Used by another module:

/**
 * @providesModule ExampleComponent
 * @flow
 */
var ExampleStyles = require('ExampleStyles');
var React = require('react-native');
var {View} = React;
var ExampleComponent = React.createClass({
  render() {
    return <View style={ExampleStyles.button} />;
  },
});
module.exports = ExampleComponent;

Flow (v0.7.0) says "property button \ Property not found in ExampleStyles.js".

@ide
Copy link
Author

ide commented Apr 4, 2015

Supporting this case in a general way would be even better, since there are projects like https://github.com/pjjanak/react-native-nested-stylesheets that could benefit too.

@samwgoldman
Copy link
Member

Immutable.Record has the same kind of API, where a constructor function takes an object and returns an object with the same properties (and others).

@ide
Copy link
Author

ide commented Apr 4, 2015

One thing is that Flow seems to handle stylesheets within the same JS file fine, but if the stylesheet object is imported from another module it has issues. Immutable records might have the same problem (haven't tried it but the function signature is pretty similar to StyleSheet's).

StyleSheet.js:
static create(obj: {[key: string]: any}): {[key: string]: number}

Immutable.js:
new (values: {[key: string]: any}): Map<string, any>

@nmn
Copy link
Contributor

nmn commented Jul 30, 2015

I have been dealing with this problem recently. The best way as of now is to define your styles like this:

/**
 * @flow
 */
var {StyleSheet} = require('react-native');
var ExampleStyles = {
  button: {},
};
var toExport: typeof ExampleStyles = StyleSheet.create(ExampleStyles);
module.exports = toExport

This will give you exactly the bahaviour you want. Using an incorrect key will throw an error, and autocomplete should potentially work.

Ideally, declaring the type of the Stylesheet.create method should do the same, but so far that hasn't worked for me:

declare module 'react-native' {
  declare class StyleSheet {
    declare function create<T: Object>(styles: T): T;
  }
}

Even this hasn't worked for me:

type Identity<T> = (in: T) => T 
var create: Identity = StyleSheet.create

Maybe there is a slightly different syntax for this??

@aaronjensen
Copy link

Something like this works for aphrodite:

declare module "aphrodite" {
  /**
   * Aphrodite style declaration
   */
  declare type StyleDeclaration = {
    [key: string]: CSSProperties;
  }

  declare type StyleDefinition = { _name: string, _definition: Object };
  declare type StyleSheetDefinition<T> = { [key: $Enum<T>]: StyleDefinition };

  declare interface StyleSheetStatic {
    /**
     * Create style sheet
     */
    create<T: StyleDeclaration>(styles: T): StyleSheetDefinition<T>;
    /**
     * Rehydrate class names from server renderer
     */
    rehydrate(renderedClassNames: string[]): void;
  }
}

The key is in:

  declare type StyleSheetDefinition<T> = { [key: $Enum<T>]: StyleDefinition };

This typechecks rightly, but autocomplete doesn't work unfortunately.

@nmn
Copy link
Contributor

nmn commented Sep 11, 2016

@aaronjensen I've been trying to solve the problem on both sides.

  1. StyleSheet.create takes and object and returns an object. This is easy enough to write a type for:
declare module 'aphrodite' {
  declare var exports: {
    StyleSheet: {
      create: <O: Object>(styles: O) => {[key: $Keys<O>]: string}
    }
  }
}

Here, O is a subtype of just object. Ideally, we could write a really huge type that type checks all of CSS, but this is slow, and will not work perfectly anyway as different CSS-in-JS libraries support different parts of the spec.

The $Keys part (same as $Enum, though I think $Keys is the new canonical name now) should work correctly in theory, but I have seen autocomplete not working with it too. That I think is something that should be marked as a bug for Flow to solve.

@aaronjensen
Copy link

@nmn Here is my complete types for aphrodite, based on the typescript ones from definitely typed: https://gist.github.com/aaronjensen/7151830b15e3bb35485763c5c89c7033

Good to know about $Keys replacing $Enum, thanks.

I opened a bug for the autocomplete issue: #2433

@nmn
Copy link
Contributor

nmn commented Sep 11, 2016

@aaronjensen Those types look pretty epic. Perhaps many of the anys can be replaced by number and string.

Also, would you submit your types for aphrodite to FlowTyped please!

@ide
Copy link
Author

ide commented Sep 11, 2016

We fixed this in React Native with $Keys, Flow shouldn't need to special case StyleSheet anymore.

@vkurchatkin
Copy link
Contributor

I'm going to close this, it seems that $ObjMap solves this issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants