reactjs/react-docgen

Add support for imported propTypes

Closed this issue Β· 50 comments

Importing prop types is a fairly common use case which doesn't appear to be possible at this stage. Any plans to support this pattern?

Example:

// defaultPropTypes.js

import { PropTypes } from 'react';

export default {
    prop1: PropTypes.string
};
// otherPropTypes.js

import { PropTypes } from 'react';

export default {
    prop2: PropTypes.object
};
// MyComponent.jsx

import { Component } from 'react';
import defaultPropTypes from './defaultPropTypes.js';
import otherPropTypes './otherPropTypes.js';

export default class MyComponent extends Component {
    static propTypes = {
        ...defaultPropTypes,
        ...otherPropTypes
    }
    // etc...
}

Results in:

{
  "MyComponent.jsx": {
    "description": ""
  }
}

I guess we could do this for simple cases, where a variable is spread into propTypes (i.e. ...foo, but not ..foo.bar) and that variable resolves to a module import.

I didn't want to make react-docgen make any assumptions about how module identifiers are resolved, but I guess it's fair to assume that it will be CommonJS compatible (i.e. module identifiers refer to npm modules or local files).

Then we could inspect the other file and if it returns an object literal, inspect and merge it as well.

+1 :)

That would be a great feature

I've got a similar usecase, but not using spread when injecting the props.

// ILink.js
import {PropTypes} from 'react';

export default PropTypes.shape({
  url: PropTypes.string.isRequired,
  selected: PropTypes.bool,
  target: PropTypes.string,
  title: PropTypes.string.isRequired
});
// MyComponent.jsx

import { Component } from 'react';
import ILink from './ILink.js';
import otherPropTypes './otherPropTypes.js';

export default class MyComponent extends Component {
    static propTypes = {
        link: ILink
    }
    // etc...
}
danez commented

The same idea would also apply to flow types, as they can be also imported. So maybe this "import something" could be abstracted in a way so it makes imported propTypes and flowTypes possible.

Even we stuck with similar use case, any plans to add support for external imports of propTypes, below is our use case.

constants.js
const BUTTON_STYLES = ['A', 'B', 'C', 'D'];
export default BUTTON_STYLES;
component.js
import BUTTON_STYLES from 'constants';
propTypes = {
kind: PropTypes.oneOf(constants),
}

class SomeComp extends React.Component{
static propTypes = propTypes;
}

export default SomeComp;
Generated AST:
'kind': {
            'type': {
                'name': 'enum',
                'computed': true,
                'value': 'BUTTON_STYLES'
            },
            'required': false,
            'description': 'some description',
            'defaultValue': {
                'value': 'A',
                'computed': false
            }
        },
Expected AST:

value of AST should be as below(which is working fine when BUTTON_STYLES are in same file, but not with import)

'kind': {
            'type': {
                'name': 'enum',
                'value': [
                    {
                        'value': 'A',
                        'computed': false
                    },
                    {
                        'value': 'B',
                        'computed': false
                    },
                    {
                        'value': 'C',
                        'computed': false
                    },
                    {
                        'value': 'D',
                        'computed': false
                    }
                ]
            },
            'required': false,
            'description': 'Button has a a different set of `kinds`',
            'defaultValue': {
                'value': 'A',
                'computed': false
            }
        }
asis commented

What do you think about a simpler approach? In our case, it would be enough to document our components with a generic message whenever a spread is found.

So, for a component like this one:

MyComponent.propTypes = {
  /**
    * Component Foo proptypes
    */
  ...Foo.propTypes,
  /**
    * Some other proptypes
    */
  ...propTypes,
  /**
    * Unhandled spread
    */
  ...{
    bar: Prop.bool,
  }
};

We would like to get the following descriptors:

{
  "...Foo.propTypes": {
    "description": "Component Foo proptypes"
  },
  "...propTypes": {
    "description": "Some other proptypes"
  }
}

Right now, these proptypes are just ignored, resulting in incomplete docs :(

We have got a proof of concept fork which adds support for Identifier or MemberExpression spreads, ignoring other spread expressions.

I can create a PR if you think this approach is worth it :)

asis commented

Oh, sorry! I was focused on extracting docs from the props declaration, so when I saw

it('understands and ignores the spread operator', () => {
I thought spread props were just being ignored altogether 😊

Thanks for the quick response!

RIP21 commented

+1
Have same use case as @asis We have our wrappers around semantic-ui components very often, and I try to spread their propTypes to have their props documented as well as ours but got nothing.
Just wrote some makes-no-sense component for test and parse it and got:

{
  "description": "Our wrapper component around semantic-ui button\r\nFor more props visit\r\n[semantic-ui-button](https://react.semantic-ui.com/elements/button#button-example-button)",
  "methods": [],
  "props": {
    "children": {
      "type": {
        "name": "any"
      },
      "required": false,
      "description": "Button label"
    }
  },
  "composes": [
    "semantic-ui-react"
  ]
}

Code:

import React from "react";
import PT from "prop-types";
import { Button as SButton } from "semantic-ui-react";

/**
 * Our wrapper component around semantic-ui button
 * For more props visit
 * [semantic-ui-button](https://react.semantic-ui.com/elements/button#button-example-button)
 */
const Button = ({ children, ...rest }) =>
  <SButton {...rest}>{children}</SButton>;

Button.propTypes = {
  /** Button label */
  children: PT.any,
  ...SButton.propTypes
};

export default Button;

Will be awesome to have this with comments that propTypes of the imported component have. Since all semantic and our components are react-docgen documented.

@RIP21
The recommended approach (which is also what we do at Facebook), is to merge the docs yourself. I.e. since you know that this component composes semantic-ui-react, you can look up the documentation for the imported component.

I guess in your specific case we would also have to record which export you are importing (Button).

I'm running into the same thing while using either propTypes = CustomPropTypes; or trying to destructure as propTypes = {...CustomPropTypes};. Is there any work around currently or is this still being discussed? I apologize but reading through the comments here it was not clear to me what the proposed solution is.

asis commented

We just used the list of "composed props" to infer the source files where they were defined. To solve the problem in styleguidist/react-styleguidist#548, we use a custom propsParser which turns each composed props reference into a "custom" prop, with a description containg a link to the relevant section of our docs. It is brittle... but it works.

I have over come this limitation by writing my own custom handler, And would like to thank @benjamn for providing such a wonderful library called recast, which makes my life easy while dealing with AST. And big thanks to react-docgen too.

Would you mind sharing your handler?

@reintroducing , Unfortunately I can't share it now, If possible will publish here for sure.

@pasupuletics, how about now? Care to share your custom handler?

So this is still not a thing?

@workjalexanderfox , Here you go https://github.com/pasupuletics/learning/blob/master/react-docgen-external-proptypes-handler.js

Just go through react-docgen custom handler documentation to configure.

RIP21 commented

This feature is really vital, especially for styleguides etc. Can somebody integrate such finally :(

@pasupuletics That's awesome!!! Works a treat, thanks!

@RIP21: Have you tried using @pasupuletics' custom handler?

Can somebody integrate such finally :(

Supporting dependency resolution in such a way that it works reliably in various environments is not trivial. That's why I'm suggesting that you are using a custom handler that works specifically for your environment.

Of course someone could write a reusable handler that works with plain CommonJS, someone else could provide one that understands with webpack, etc.

I don't have the bandwidth to build and support all this.

@pasupuletics can you please add some license to your repo?

If you find software that doesn’t have a license, that generally means you have no permission from the creators of the software to use, modify, or share the software. Although a code host such as GitHub may allow you to view and fork the code, this does not imply that you are permitted to use, modify, or share the software for any purpose.

https://choosealicense.com/no-permission/

You've added UNLICENSED to your package.json, but that's probably not what you actually want:

Finally, if you do not wish to grant others the right to use a private or unpublished package under any terms:

{ "license": "UNLICENSED" }

https://docs.npmjs.com/files/package.json#license

@pasupuletics I hope you don't mind, I published the handler on npm and documented it: https://github.com/siddharthkp/react-docgen-external-proptypes-handler

@siddharthkp , Thats really awesome. No issues. Glad to see my name in repo.

JuhQ commented

This issue has been opened in 2015 and still open. Any progress on fixing this?

danm commented

I'm here as I'm using docz which uses docgen in its pipeline. Each of my components have their own directory with several files at the root which abstracted from the main component, one of those is a Flow file which annotates the component and imports its types. Because docgen can't read this file, none of my props generate for the prop table.

@danez you mentioned this earlier and looks still to be an issue. Any ideas where could get started on this to create a PR?

@pasupuletics @siddharthkp If I understand correctly (I have downloaded and tried this handler in my project), that handler only works for imported constants but not for actual imported propTypes? I have entire propTypes declared in external files which I would like to use.

@benwiley4000 , It should work for both. If you never mind, can you please share your project config.

@pasupuletics thanks for following up! here's my styleguidist config which specifies the docgen handlers. https://github.com/benwiley4000/cassette/blob/48ab0a5ea187d4458df6933927b6cc1522cc1469/styleguide.config.js#L180

In particular I'm wanting this propType (repeatStrategy) to show up as an enum which should be one of "none", "playlist" or "track" as specified here.

@benwiley4000 seems above URLs are broken.

@pasupuletics sorry I corrected the links now.

@pasupuletics and @siddharthkp got me inspired with their implementations. I forked their repo and expanded the functionality, wrote a whole lot of snapshot tests, and in the end mostly tweaked some code related to resolveToValue.

I'd like to offer this up in hopes that it will help others: https://github.com/benjroy/react-docgen-imported-proptype-handler

npm install react-docgen-imported-proptype-handler --save-dev

Setup is pretty much the same as react-docgen-external-proptypes-handler

The test fixtures exercise the expanded capabilities: https://github.com/benjroy/react-docgen-imported-proptype-handler/tree/master/src/handlers/__tests__/fixtures/components

And there is nothing computed in the final snapshot: https://github.com/benjroy/react-docgen-imported-proptype-handler/blob/master/src/handlers/__tests__/__snapshots__/importedPropTypeHandler-test.js.snap

it works well following imports of files in your repo.

following imports from node_modules: there is some basic path resolution and an implementation exercising it to make sure it won't throw, but I didn't write any logic to be able to read babel processed files from external dependencies

@benjroy thank you for sharing this! I have tried setting it up in my project and I ran into some issues that are keeping me from building my docs with this setup.

Do you have an example of how you've integrated the package in your own project?

@benwiley4000 thank you for trying it out! I forgot to ship @babel/runtime. Just published v1.0.1 for the fix.

Additionally, here is a repo with basic example usage:
https://github.com/benjroy/react-docgen-imported-proptype-handler-example
And the script itself:
https://github.com/benjroy/react-docgen-imported-proptype-handler-example/blob/master/scripts/docgen.js

@benjroy That's brilliant!

If it works nicely, I don't mind deprecating my package. I'm not actively maintaining it anyways.

You can add @pasupuletics and me as maintainers if you like.

@benjroy Thanks for doing this. I'm unclear what the differences between your solution and this one is, however. Also, I think it would be good to show how this is used in the confines of react-styleguidist which is I think where all this originated, if possible. I'm also not able to see clearly how to use it there from your example.

@pasupuletics @siddharthkp and @benjroy , thanks for the great work!

I'm trying react-docgen-imported-proptype-handler as it has the most robust set of supported cases and fixtures, but I'm getting the following when doing this:

import BaseComponent from './BaseComponent';

// ...

MyComponent.propTypes = {
  ...BaseComponent.propTypes,
  // ... more
}

MyComponent.defaultProps = {
  ...BaseComponent.defaultProps,
  // ... more
}

export default MyComponent;

Error:

Cannot find module 'react-docgen/dist/babylon' from 'resolveImportedNamespace.js'
  at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:221:17)
  at Object.<anonymous> (node_modules/react-docgen-imported-proptype-handler/dist/utils/resolveImportedNamespace.js:24:39)

Since there is not error without your handler, and all the Babel stuff works nicely, I'm a bit stumped.
react-docgen and react-docgen-imported-proptype-handler both use recast@0.16.1, but I've got a different react-docgen version floating around from babel-plugin-react-docgen:

$ yarn list --pattern react-docgen

yarn list v1.13.0
β”œβ”€ babel-plugin-react-docgen@2.0.0
β”‚  └─ react-docgen@3.0.0-rc.2
β”œβ”€ react-docgen-external-proptypes-handler@1.0.2
β”œβ”€ react-docgen-imported-proptype-handler@1.0.4
└─ react-docgen@3.0.0

It seems that these issues could be side-stepped if this handler were simply a pull-request agains the official react-docgen repo, no? You're already copy/pasting a bunch of utils/ over, and the demand for this handler is so great that I'd be surprized if it didn't get accepted, right @fkling ?

@nemoDreamer yea, I think this handler I wrote would be better implemented as a PR into react-docgen (the meat of the changes is slight tweaks to some of the utility methods to pass a filepath around).

Your problem specifically is that react-docgen v3 doesn't ship the babylon parser.

I noticed too late when i was working on the handler that react-docgen was in alpha for major version 3.0.0, but tl;dr;:
this handler was only tested with react-docgen ^2.x.x

  • you will get bit by the babel-plugin-react-docgen shipping docgen v3
  • you will probably get bit by recast (this bit me because there were two competing versions of recast in my node deps tree. In order for the handler to work, docgen and this handler MUST be pointing to the exact same recast dependency in node-modules

Reworking this for react-docgen v3.0.0 will probably require a bit more work because of the dropped babylon dep, and at that point the effort would be much better spent working this directly into react-docgen itself

Haven't tried your package yet @benjroy but if it fixes the problem, it'd be awesome to see it merged into react-docgen because a lot of people seem to have the same issue and it's been 3 years since it was created

Can this also solve the same problem with imported flow types?

any progress here?

I started hacking on this today and managed to get importing working for propTypes, flow, and typescript types. See devongovett/react-docgen@typescript...devongovett:importing. I'll open up a PR once the typescript PR (#348) is merged, since this builds on that. In the meantime, feel free to try out my branch.

Current limitations:

  • ES6 imports only. Could add CommonJS support, but it's a bit more complicated and I'm not sure it's worth it when most apps should be using ES6 now anyway.
  • Namespace imports are unsupported (import * as foo from '...')
  • Wildcard re-exports are unsupported (export * from '...')
  • Resolving from node_modules is supported, but flow libdefs (flow-typed folder, .js.flow files, and .d.ts files) are unsupported. Would need to reimplement a custom resolver for those.

Please let me know if you try it out and encounter bugs.

PR opened: #352

There is an issue with PropTypes.oneOf(['a', 'b']). It creates next error: TypeError: Cannot read property 'type' of null

I have the same problem, is this being worked on?
@Tauana-Pacheco FYI

Dropping a link to this conversation to help my future self when I inevitably end up here in a couple months.

RIP21 commented

Dropping a link to this conversation to help my future self when I inevitably end up here in a couple months.

Just my 5 cents. Anyone who is still using prop-types should really invest in Typescript instead. Prop-types are quite dead at this point. While, I'm sure, many are still using prop-types, it is extremely dated approach and causes more issues than solves IMO. There are plenty of codemodes that can help you with migration.

@RIP21 the linked conversation is relevant for Typescript types...