oliviertassinari / babel-plugin-transform-react-remove-prop-types

Remove unnecessary React propTypes from the production build. :balloon:
MIT License
897 stars 61 forks source link

Doesn’t remove propTypes in certain situations #196

Open jaydenseric opened 4 years ago

jaydenseric commented 4 years ago

For all of the following examples, propTypes are incorrectly not removed:

import PropTypes from 'prop-types';

export default function Foo({ children }) {
  return children();
}

Foo.propTypes = {
  children: PropTypes.func.isRequired,
};

Babel REPL

import PropTypes from 'prop-types';

export default function Foo({ children }) {
  return children;
}

Foo.propTypes = {
  children: PropTypes.node,
};
import PropTypes from 'prop-types';

export default function Foo({ children }) {
  return <>{children}</>;
}

Foo.propTypes = {
  children: PropTypes.node,
};

While this works:

import PropTypes from 'prop-types';

export default function Foo({ children }) {
  return <div>{children}</div>;
}

Foo.propTypes = {
  children: PropTypes.node,
};
jaydenseric commented 4 years ago

These situations may look edge case, but really they come up fairly often - maybe 5% of our project components.

Some examples:

import PropTypes from 'prop-types';
import { useEffect } from 'react';
import TagManager from 'react-gtm-module';

export default function GTMEventViewSearchResults({ categoryId }) {
  useEffect(() => {
    if (process.env.GTM_ID && categoryId)
      TagManager.dataLayer({
        dataLayer: {
          event: 'view_search_results',
          id: categoryId,
        },
      });
  }, [categoryId]);

  return null;
}

GTMEventViewSearchResults.propTypes = {
  categoryId: PropTypes.string.isRequired,
};
import { MapContext } from '@urbica/react-map-gl';
import PropTypes from 'prop-types';
import { useContext } from 'react';
import useSupercluster from 'use-supercluster';

/**
 * An alternative to the [`@urbica/react-map-gl-cluster`](https://github.com/urbica/react-map-gl-cluster)
 * component [`Cluster`](https://urbica.github.io/react-map-gl-cluster/#/Cluster).
 * @param {object} options Options.
 * @param {Array<object>} options.points Supercluster points; an array of [GeoJSON Feature](https://tools.ietf.org/html/rfc7946#section-3.2) objects. Each feature’s `geometry` must be a [GeoJSON Point](https://tools.ietf.org/html/rfc7946#section-3.1.2).
 * @param {object} [options.options] Supercluster options.
 * @param {Function} options.children React children render function.
 * @returns {ReactNode} React children containing map markers.
 * @see [GitHub issue for `Cluster` children not re-rendering on state updates](https://github.com/urbica/react-map-gl-cluster/issues/31).
 */
export default function MapCluster({ points, options, children }) {
  const map = useContext(MapContext);
  const { supercluster, clusters } = useSupercluster({
    points,
    bounds: map.getBounds().toArray().flat(),
    zoom: map.getZoom(),
    options,
  });

  return children(clusters, supercluster);
}

MapCluster.propTypes = {
  points: PropTypes.arrayOf(
    PropTypes.exact({
      type: PropTypes.oneOf(['Feature']).isRequired,
      geometry: PropTypes.exact({
        type: PropTypes.oneOf(['Point']).isRequired,
        coordinates: PropTypes.arrayOf(PropTypes.number.isRequired).isRequired,
      }),
      properties: PropTypes.object,
    })
  ),
  options: PropTypes.object,
  children: PropTypes.func.isRequired,
};
GalGrigoryeva commented 3 years ago

I have one more example when propTypes are not removed:

import _RawContent from 'some-external-lib';

const RawContent = React.memo(props => (
  <_RawContent {...props}>
    {props.children}
  </_RawContent>
));

RawContent.propTypes = {
  children: _RawContent.propTypes.children,
};

export { RawContent };

But if I rewrite this functional component adding return

...
const RawContent = React.memo(props => {
  return (
    <_RawContent {...props}>
      {props.children}
    </_RawContent>
  );
});

...

or doing something like that

...
const RawContentComponent = props => (
  <_RawContent {...props}>
    {props.children}
  </_RawContent>
);

RawContentComponent.propTypes = {
  children: _RawContent.propTypes.children,
};

export const RawContent = React.memo(RawContentComponent);

or even changing named export into default

...

const RawContent = props => (
  <_RawContent {...props}>
    {props.children}
  </_RawContent>
);

RawContent.propTypes = {
  children: _RawContent.propTypes.children,
};

export default React.memo(RawContent);

everything works fine.

We discovered this problem only because of this line children: _RawContent.propTypes.children, as we got the error raw-content.jsx:14 Uncaught TypeError: Cannot read property 'children' of undefined.