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

Class constructor doesn't have reference to this.props #8583

Closed
aray12 opened this issue Aug 30, 2018 · 7 comments
Closed

Class constructor doesn't have reference to this.props #8583

aray12 opened this issue Aug 30, 2018 · 7 comments
Labels
outdated A closed issue/PR that is archived due to age. Recommended to make a new issue

Comments

@aray12
Copy link

aray12 commented Aug 30, 2018

v7 Regression

First check out: https://new.babeljs.io/docs/en/next/v7-migration.html
Also a partial upgrade tool: https://github.com/babel/babel-upgrade

Potential Commit/PR that introduced the regression
If you have time to investigate, what PR/date introduced this issue.

This is my first intro to babel 7

Describe the regression
A clear and concise description of what the regression is.

I use babel-upgrade in order to upgrade to babel 7 and everything appears to be working perfectly except one thing. We have a reference to this.props in our constructor in a couple of places that was working before, but now this.props seems to be undefined. We do call super(props) prior to referencing them.

Input Code

class MyComp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      someVar: this.props.someInitialVar // <-- Uncaught TypeError: Cannot read property 'someInitialVar' of undefined
    };
  }

  render() {
   // ...
  }
}

Babel Configuration (.babelrc, package.json, cli command)

{
  presets: [
    ['@babel/env', { targets: ['last 2 versions', 'ie >= 9', 'and_chr >= 2.3'] }],
    '@babel/react',
    '@babel/flow',
  ],
  plugins: [
    'emotion',
    '@babel/plugin-proposal-object-rest-spread',
    '@babel/plugin-proposal-class-properties',
    '@babel/plugin-transform-async-to-generator',
    '@babel/plugin-syntax-dynamic-import',
    '@babel/plugin-transform-flow-comments',
    'dev-expression',
    ['lodash', { id: ['lodash', 'recompose'] }],
  ],
};

Expected behavior/code
A clear and concise description of what you expected to happen (or code).

Environment

  • Babel version(s): [e.g. v6.0.0, v7.0.0-beta.34]: v7
  • Node/npm version: [e.g. Node 8/npm 5]: Node 8
  • OS: [e.g. OSX 10.13.4, Windows 10]: docker node
  • Monorepo [e.g. yes/no/Lerna]
  • How you are using Babel: loader
@babel-bot
Copy link
Collaborator

Hey @aray12! We really appreciate you taking the time to report an issue. The collaborators
on this project attempt to help as many people as possible, but we're a limited number of volunteers,
so it's possible this won't be addressed swiftly.

If you need any help, or just have general Babel or JavaScript questions, we have a vibrant Slack
community that typically always has someone willing to help. You can sign-up here
for an invite.

@loganfsmyth
Copy link
Member

Would you be able to look in the bundled JS file from Webpack to see what Babel's compiled output for this class looks like?

@chiplay
Copy link

chiplay commented Aug 30, 2018

I'm having similar issues with Typescript + Babel 7. Class properties are getting set to void 0 in the constructor after super() is called. So any class properties that are getting set as part of the super chain are getting reset to undefined.

@loganfsmyth
Copy link
Member

@chiplay I don't know if that's related to this issue or not, but for yours I'd recommend looking at #8417

@aray12
Copy link
Author

aray12 commented Aug 31, 2018

Hey sorry for the late response. The following is the component that is causing the problem, but when I moved the offending line into componentDidMount, I got the same error with the same conditions in another component. In this case, it is my use of getRef inside of the constructor that causes the bug (please don't judge my code too harshly )

// @flow

import React, { Component } from 'react';
import ReactModal from 'react-modal';
import cx from 'classnames';
import { IndexLink } from 'react-router';
import { compose, withProps } from 'recompose';

ReactModal.setAppElement('#app');

import styles from './Modal.local.scss';

import SkillxLogo from '../assets/sx-logo.react.svg';
import BackButton from 'components/Navigation/Container/Shell/BackButton/BackButton';
import Icon from 'components/Visual/Icon/Icon';

import { withBreakpoints, BREAKPOINTS } from 'features/Breakpoints';
import { Scrollbars } from 'features/Scrollbars';

import { DEFAULT_LOADING_ANIMATION_DELAY_MODAL } from 'constants/misc';

const decorate = compose(
  withBreakpoints(),
  withProps(({ isSmallerThan }) => ({
    isMobile: isSmallerThan(BREAKPOINTS.LARGE),
  }))
);

type Props = {
  isOpen: boolean,
  children: any,
  onRequestClose?: Function,
  size: 'mid' | 'large',
  isLoading: boolean,
  loadingDelay: number,
  isClosable: boolean,
  isMobile: boolean,
  getRef?: Function,
};

type State = {
  bodyOpenClassName: string,
  isDelayed: boolean,
};

class Modal extends Component<Props, State> {
  props: Props;

  state: State;

  modalRef: any;
  contentRef: any;

  timeout: number;

  static defaultProps = {
    size: 'mid',
    loadingDelay: DEFAULT_LOADING_ANIMATION_DELAY_MODAL,
    isClosable: true,
  };

  constructor(props: Props) {
    super(props);

    this.state = {
      bodyOpenClassName: `ReactModal__Body--open--${Math.random()
        .toString(36)
        .substring(5)}`,
      isDelayed: true,
    };

    if (this.props.getRef) this.props.getRef(this);
  }

  componentDidMount() {
    const { isLoading, loadingDelay } = this.props;

    if (isLoading) {
      this.timeout = setTimeout(
        () => this.setState({ isDelayed: false }),
        loadingDelay
      );
    }
  }

  componentDidUpdate(prevProps: Props) {
    const { isLoading, loadingDelay } = this.props;

    if (!prevProps.isLoading && isLoading) {
      this.timeout = setTimeout(
        () => this.setState({ isDelayed: false }),
        loadingDelay
      );
    }

    if (prevProps.isLoading && !isLoading) {
      clearTimeout(this.timeout);
      this.setState({ isDelayed: true }); // eslint-disable-line react/no-did-update-set-state
    }
  }

  componentWillUnmount() {
    clearTimeout(this.timeout);
  }

  scrollToTop = () => {
    if (this.props.isMobile) {
      if (this.contentRef) {
        this.contentRef.parentNode.scrollTop = 0;
      }
    } else if (this.modalRef) {
      this.modalRef.portal.overlay.scrollTop = 0;
    }
  };

  render() {
    const {
      children,
      onRequestClose,
      size,
      isLoading,
      isClosable,
      isMobile,
      ...rest
    } = this.props;

    const { isDelayed } = this.state;

    return (
      <ReactModal
        bodyOpenClassName={this.state.bodyOpenClassName}
        overlayClassName={{
          base: cx(styles.overlay, styles[size]),
          afterOpen: styles.afterOpen,
          beforeClose: styles.beforeClose,
        }}
        className={{
          base: cx(styles.modalWrap, styles[size], {
            [styles.loading]: isLoading && !isDelayed,
          }),
          afterOpen: styles.afterOpen,
          beforeClose: styles.beforeClose,
        }}
        closeTimeoutMS={500}
        contentLabel="Modal"
        onRequestClose={onRequestClose}
        shouldReturnFocusAfterClose={false}
        {...rest}
        ref={c => {
          if (c) {
            this.modalRef = c;
          }
        }}
      >
        <div
          className={cx(styles.modal, styles[size], {
            [styles.loading]: isLoading && !isDelayed,
          })}
        >
          <div className={cx('text-center', styles.header, styles[size])}>
            {isClosable && (
              <BackButton
                onClick={onRequestClose}
                className="hide-for-large margined-left-small"
              />
            )}
            <IndexLink tabIndex="-1" to="/" className={styles.logo}>
              <SkillxLogo />
            </IndexLink>
            {isClosable && (
              <button
                onClick={onRequestClose}
                className={cx(styles.closeBtn, 'show-for-large')}
                tabIndex="-1"
              >
                <Icon
                  icon={['fal', 'times']}
                  size="2x"
                  className="color-medium-gray"
                />
              </button>
            )}
          </div>
          {!isMobile && (
            <div className={cx(styles.content, 'row')}>{children}</div>
          )}
          {isMobile && (
            <Scrollbars style={{ height: 'calc(100vh - 50.5px)' }}>
              <div
                ref={ref => {
                  this.contentRef = ref;
                }}
                className={cx(styles.content, 'row')}
              >
                {children}
              </div>
            </Scrollbars>
          )}
        </div>
      </ReactModal>
    );
  }
}

export default decorate(Modal);

The output is this

import _withProps from "recompose/withProps";
import _compose from "recompose/compose";
var _jsxFileName = "/opt/app/src/components/Modal/Modal.js";

function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }

function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }

function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }

function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }

function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }

function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }

function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

import React, { Component } from 'react';
import ReactModal from 'react-modal';
import cx from 'classnames';
import { IndexLink } from 'react-router';
ReactModal.setAppElement('#app');
import styles from './Modal.local.scss';
import SkillxLogo from '../assets/sx-logo.react.svg';
import BackButton from 'components/Navigation/Container/Shell/BackButton/BackButton';
import Icon from 'components/Visual/Icon/Icon';
import { withBreakpoints, BREAKPOINTS } from 'features/Breakpoints';
import { Scrollbars } from 'features/Scrollbars';
import { DEFAULT_LOADING_ANIMATION_DELAY_MODAL } from 'constants/misc';

var decorate = _compose(withBreakpoints(), _withProps(function (_ref) {
  var isSmallerThan = _ref.isSmallerThan;
  return {
    isMobile: isSmallerThan(BREAKPOINTS.LARGE)
  };
}));
/*:: type Props = {
  isOpen: boolean,
  children: any,
  onRequestClose?: Function,
  size: 'mid' | 'large',
  isLoading: boolean,
  loadingDelay: number,
  isClosable: boolean,
  isMobile: boolean,
  getRef?: Function,
};*/

/*:: type State = {
  bodyOpenClassName: string,
  isDelayed: boolean,
};*/


var Modal =
/*#__PURE__*/
function (_Component) {
  _inherits(Modal, _Component);

  function Modal(props
  /*: Props*/
  ) {
    var _this;

    _classCallCheck(this, Modal);

    _this = _possibleConstructorReturn(this, _getPrototypeOf(Modal).call(this, props));

    _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "props", void 0);

    _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "state", void 0);

    _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "modalRef", void 0);

    _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "contentRef", void 0);

    _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "timeout", void 0);

    _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "scrollToTop", function () {
      if (_this.props.isMobile) {
        if (_this.contentRef) {
          _this.contentRef.parentNode.scrollTop = 0;
        }
      } else if (_this.modalRef) {
        _this.modalRef.portal.overlay.scrollTop = 0;
      }
    });

    _this.state = {
      bodyOpenClassName: "ReactModal__Body--open--".concat(Math.random().toString(36).substring(5)),
      isDelayed: true
    };
    // NEXT LINE -- Uncaught TypeError: Cannot read property 'getRef' of undefined
    if (_this.props.getRef) _this.props.getRef(_assertThisInitialized(_assertThisInitialized(_this)));
    return _this;
  }

  _createClass(Modal, [{
    key: "componentDidMount",
    value: function componentDidMount() {
      var _this2 = this;

      var _this$props = this.props,
          isLoading = _this$props.isLoading,
          loadingDelay = _this$props.loadingDelay;

      if (isLoading) {
        this.timeout = setTimeout(function () {
          return _this2.setState({
            isDelayed: false
          });
        }, loadingDelay);
      }
    }
  }, {
    key: "componentDidUpdate",
    value: function componentDidUpdate(prevProps
    /*: Props*/
    ) {
      var _this3 = this;

      var _this$props2 = this.props,
          isLoading = _this$props2.isLoading,
          loadingDelay = _this$props2.loadingDelay;

      if (!prevProps.isLoading && isLoading) {
        this.timeout = setTimeout(function () {
          return _this3.setState({
            isDelayed: false
          });
        }, loadingDelay);
      }

      if (prevProps.isLoading && !isLoading) {
        clearTimeout(this.timeout);
        this.setState({
          isDelayed: true
        });
      }
    }
  }, {
    key: "componentWillUnmount",
    value: function componentWillUnmount() {
      clearTimeout(this.timeout);
    }
  }, {
    key: "render",
    value: function render() {
      var _this4 = this;

      var _this$props3 = this.props,
          children = _this$props3.children,
          onRequestClose = _this$props3.onRequestClose,
          size = _this$props3.size,
          isLoading = _this$props3.isLoading,
          isClosable = _this$props3.isClosable,
          isMobile = _this$props3.isMobile,
          rest = _objectWithoutProperties(_this$props3, ["children", "onRequestClose", "size", "isLoading", "isClosable", "isMobile"]);

      var isDelayed = this.state.isDelayed;
      return React.createElement(ReactModal, _extends({
        bodyOpenClassName: this.state.bodyOpenClassName,
        overlayClassName: {
          base: cx(styles.overlay, styles[size]),
          afterOpen: styles.afterOpen,
          beforeClose: styles.beforeClose
        },
        className: {
          base: cx(styles.modalWrap, styles[size], _defineProperty({}, styles.loading, isLoading && !isDelayed)),
          afterOpen: styles.afterOpen,
          beforeClose: styles.beforeClose
        },
        closeTimeoutMS: 500,
        contentLabel: "Modal",
        onRequestClose: onRequestClose,
        shouldReturnFocusAfterClose: false
      }, rest, {
        ref: function ref(c) {
          if (c) {
            _this4.modalRef = c;
          }
        },
        __source: {
          fileName: _jsxFileName,
          lineNumber: 139
        }
      }), React.createElement("div", {
        className: cx(styles.modal, styles[size], _defineProperty({}, styles.loading, isLoading && !isDelayed)),
        __source: {
          fileName: _jsxFileName,
          lineNumber: 165
        }
      }, React.createElement("div", {
        className: cx('text-center', styles.header, styles[size]),
        __source: {
          fileName: _jsxFileName,
          lineNumber: 170
        }
      }, isClosable && React.createElement(BackButton, {
        onClick: onRequestClose,
        className: "hide-for-large margined-left-small",
        __source: {
          fileName: _jsxFileName,
          lineNumber: 172
        }
      }), React.createElement(IndexLink, {
        tabIndex: "-1",
        to: "/",
        className: styles.logo,
        __source: {
          fileName: _jsxFileName,
          lineNumber: 177
        }
      }, React.createElement(SkillxLogo, {
        __source: {
          fileName: _jsxFileName,
          lineNumber: 178
        }
      })), isClosable && React.createElement("button", {
        onClick: onRequestClose,
        className: cx(styles.closeBtn, 'show-for-large'),
        tabIndex: "-1",
        __source: {
          fileName: _jsxFileName,
          lineNumber: 181
        }
      }, React.createElement(Icon, {
        icon: ['fal', 'times'],
        size: "2x",
        className: "color-medium-gray",
        __source: {
          fileName: _jsxFileName,
          lineNumber: 186
        }
      }))), !isMobile && React.createElement("div", {
        className: cx(styles.content, 'row'),
        __source: {
          fileName: _jsxFileName,
          lineNumber: 195
        }
      }, children), isMobile && React.createElement(Scrollbars, {
        style: {
          height: 'calc(100vh - 50.5px)'
        },
        __source: {
          fileName: _jsxFileName,
          lineNumber: 198
        }
      }, React.createElement("div", {
        ref: function ref(_ref2) {
          _this4.contentRef = _ref2;
        },
        className: cx(styles.content, 'row'),
        __source: {
          fileName: _jsxFileName,
          lineNumber: 199
        }
      }, children))));
    }
  }]);

  return Modal;
}(Component);

_defineProperty(Modal, "defaultProps", {
  size: 'mid',
  loadingDelay: DEFAULT_LOADING_ANIMATION_DELAY_MODAL,
  isClosable: true
});

export default decorate(Modal);


//////////////////
// WEBPACK FOOTER
// ./src/components/Modal/Modal.js
// module id = Xpbu
// module chunks = 14

@loganfsmyth
Copy link
Member

@aray12 Cool, then this is the same issue as #8417

My recommendation would be to delete these lines

  props: Props;
  state: State;

since I don't think they are needed when you have then already passed via extends Component<Props, State>. Though if that cases issues, you can also do

  props: Props = this.state;
  state: State = this.state;

Basically you shouldn't declare a class property if you're not actually the one creating and assigning it.

@aray12
Copy link
Author

aray12 commented Aug 31, 2018

Ahh interesting. I have been continuing to do that because that is what our older version of eslint-plugin-flowtype expected. Sounds like I need to do a big revisit to our tooling now. If you don't mind, I am going to keep this open until I can confirm that this is what is causing the issue

Thanks for the help and all of the amazing work on this project!

@aray12 aray12 closed this as completed Sep 3, 2018
@lock lock bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Dec 3, 2018
@lock lock bot locked as resolved and limited conversation to collaborators Dec 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
outdated A closed issue/PR that is archived due to age. Recommended to make a new issue
Projects
None yet
Development

No branches or pull requests

4 participants