React: Warning: Expected Instance Props to Match Memoized Props

Created on 13 Nov 2018  路  18Comments  路  Source: facebook/react

When updating react and react-dom from 16.6.0 to 16.6.1 or 16.6.2, I get new warnings that did not occur prior:

Warning: Expected instance props to match memoized props before componentDidMount. This is likely due to a bug in React. Please file an issue.
Warning: Expected instance props to match memoized props before componentDidUpdate. This is likely due to a bug in React. Please file an issue.
Warning: Expected instance props to match memoized props before processing the update queue. This is likely due to a bug in React. Please file an issue.

all from react-dom.development.js?ec6d:506

I am unable to give you examples from the code as I am not sure where this is occurring and the stacktrace comes from deep in react-dom and is unfollowable from my code, though it happens on every page visited within the app. Reverting back to 16.6.0 versions removes the warning completely.

There exists at least one other with the same problem: https://stackoverflow.com/questions/53272887/expected-instance-props-to-match-memoized-props-in-react-16

Needs More Information

Most helpful comment

All 18 comments

Sorry, we鈥檒l need a reproducing example to fix this. Even if you can鈥檛 share your app鈥檚 code, here is a good guide to creating one: http://sscce.org/

I have the same problem, and I can reproducing example using my yoman generator

so the steps is

  1. npm install -g yo (if you do not install yoman)
  2. npm install -g generator-webpack-humble
  3. yo webpack-humble
  4. select use React with react-css-modules and react-redux and redux-thunk
  5. run npm run dev after all dependents installed
  6. then you'll see Warning: Expected Instance Props to Match Memoized in browser console

And I already found the key of problem is componentDidMount hook, if I removed it the warning is disappeared

Hi there,
For me, it's reproduced with react: 16.6.* and rxjs: 6.3.3. I found that without initializing of rxjs it works fine only on react: 16.6.1 and lower. Maybe it will be helpful for you to reproduce the issue.
Thanks!

UPD: reproduced only on webpack-dev-server running :)

I'm having the same issue after updating both react and react-dom to ^16.6.3. And with a bundle size of 3.6 mb excluding node modules reproducing the issue will be rough.

I have been experiencing the same issue and have isolated it to using the latest react-css-modules with create-react-class. When migrating to use ES6 class components, the error disappears.

Here is a minimal example.

Thanks to @gaearon for the suggestions to isolate the issue on /r/reactjs.

Looking at the reproducing code in https://codesandbox.io/s/xppzppmpv4, this is because the library you're using (react-css-modules) contains code like this:

    WrappedComponent.prototype.render = function render() {
      // ...

        this.props = props;

It is not supported to override this.props yourself. It's provided by React. The fact that this code even works is entirely accidental. I would suggest to file an issue with react-css-modules or migrate away from that project. It is common knowledge that React render methods are intended to not mutate data, and mutating this.props itself is very aggressive and will lead to more issues in the future.

As an alternative, of course, they could restore it back in a try/finally block.

// ...
const originalProps = this.props;
try {
  this.props = props;
  // ...
} finally {
  this.props = originalProps;
}

Again, this is not recommended, but it would work around the issue.

@gaearon Is this what you mean?

/* eslint-disable react/prop-types */

import _ from 'lodash';
import React from 'react';
import hoistNonReactStatics from 'hoist-non-react-statics';
import linkClass from './linkClass';
import renderNothing from './renderNothing';

/**
 * @param {ReactClass} Component
 * @param {Object} defaultStyles
 * @param {Object} options
 * @returns {ReactClass}
 */
export default (Component: Object, defaultStyles: Object, options: Object) => {
  const WrappedComponent = class extends Component {
    render () {
      let styles;

      const hasDefaultstyles = _.isObject(defaultStyles);

      if (this.props.styles || hasDefaultstyles) {
        const props = Object.assign({}, this.props);

        if (props.styles) {
          styles = props.styles;
        } else if (hasDefaultstyles) {
          styles = defaultStyles;
          delete props.styles;
        }

        Object.defineProperty(props, 'styles', {
          configurable: true,
          enumerable: false,
          value: styles,
          writable: false
        });

-        this.props = props;
+        const originalProps = this.props;
+
+        try {
+          this.props = props;
+        } finally {
+          this.props = originalProps;
+        }
      } else {
        styles = {};
      }

      const renderResult = super.render();

      if (renderResult) {
        return linkClass(renderResult, styles, options);
      }

      return renderNothing(React.version);
    }
  };

  return hoistNonReactStatics(WrappedComponent, Component);
};


Yes. Note that props would still be "wrong" in componentDidMount and other lifecycle methods. As well as prevProps in componentDidUpdate. Assigning them like this was never intentionally supported so I'm not actually sure what the behavior used to be with your existing code, and which APIs worked correctly.

Oh wait. Sorry. No that wouldn't be useful.

I meant something like this:

+      try {
+        this.props = props;
         const renderResult = super.render();
+      } finally {
+        this.props = originalProps;
+      }

I assumed the reason you want to assign fake this.props is so that the base render "sees" them.

To clarify. I mean that it's not supported to ever overwrite this.props in a React component. The fact that it worked in that module is an accident. There are absolutely no guarantees attached to that.

To limit the scope of the issue it introduces, my proposal is to "revert" them back right after calling super.render. I guess it still breaks event handlers etc, so maybe this isn't helpful.

@Roboonl Could you confirm whether what Dan is suggesting fixes the issue without undesired side-effects?

@gajus I'm using the NPM package (^4.7.7) for react-css-modules and the source of dist/extendReactClass.js differs from the file in the git repository so I'm not quite sure how to test this. Please let me know how I can test it and i'll give a heads up asap.

This is the source of node_modules/react-css-modules/dist/extendReactClass.js

'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _isObject2 = require('lodash/isObject');

var _isObject3 = _interopRequireDefault(_isObject2);

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _hoistNonReactStatics = require('hoist-non-react-statics');

var _hoistNonReactStatics2 = _interopRequireDefault(_hoistNonReactStatics);

var _linkClass = require('./linkClass');

var _linkClass2 = _interopRequireDefault(_linkClass);

var _renderNothing = require('./renderNothing');

var _renderNothing2 = _interopRequireDefault(_renderNothing);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _defaults(obj, defaults) { var keys = Object.getOwnPropertyNames(defaults); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = Object.getOwnPropertyDescriptor(defaults, key); if (value && value.configurable && obj[key] === undefined) { Object.defineProperty(obj, key, value); } } return obj; }

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

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : _defaults(subClass, superClass); } /* eslint-disable react/prop-types */

/**
 * @param {ReactClass} Component
 * @param {Object} defaultStyles
 * @param {Object} options
 * @returns {ReactClass}
 */
exports.default = function (Component, defaultStyles, options) {
  var WrappedComponent = function (_Component) {
    _inherits(WrappedComponent, _Component);

    function WrappedComponent() {
      _classCallCheck(this, WrappedComponent);

      return _possibleConstructorReturn(this, _Component.apply(this, arguments));
    }

    WrappedComponent.prototype.render = function render() {
      var styles = void 0;

      var hasDefaultstyles = (0, _isObject3.default)(defaultStyles);

      if (this.props.styles || hasDefaultstyles) {
        var props = Object.assign({}, this.props);

        if (props.styles) {
          styles = props.styles;
        } else if (hasDefaultstyles) {
          styles = defaultStyles;
          delete props.styles;
        }

        Object.defineProperty(props, 'styles', {
          configurable: true,
          enumerable: false,
          value: styles,
          writable: false
        });

        this.props = props;
      } else {
        styles = {};
      }

      var renderResult = _Component.prototype.render.call(this);

      if (renderResult) {
        return (0, _linkClass2.default)(renderResult, styles, options);
      }

      return (0, _renderNothing2.default)(_react2.default.version);
    };

    return WrappedComponent;
  }(Component);

  return (0, _hoistNonReactStatics2.default)(WrappedComponent, Component);
};

module.exports = exports['default'];

Well, thats just the effects of the Babel transpilation.

Something along the lines of:

'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _isObject2 = require('lodash/isObject');

var _isObject3 = _interopRequireDefault(_isObject2);

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _hoistNonReactStatics = require('hoist-non-react-statics');

var _hoistNonReactStatics2 = _interopRequireDefault(_hoistNonReactStatics);

var _linkClass = require('./linkClass');

var _linkClass2 = _interopRequireDefault(_linkClass);

var _renderNothing = require('./renderNothing');

var _renderNothing2 = _interopRequireDefault(_renderNothing);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _defaults(obj, defaults) { var keys = Object.getOwnPropertyNames(defaults); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = Object.getOwnPropertyDescriptor(defaults, key); if (value && value.configurable && obj[key] === undefined) { Object.defineProperty(obj, key, value); } } return obj; }

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

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : _defaults(subClass, superClass); } /* eslint-disable react/prop-types */

/**
 * @param {ReactClass} Component
 * @param {Object} defaultStyles
 * @param {Object} options
 * @returns {ReactClass}
 */
exports.default = function (Component, defaultStyles, options) {
  var WrappedComponent = function (_Component) {
    _inherits(WrappedComponent, _Component);

    function WrappedComponent() {
      _classCallCheck(this, WrappedComponent);

      return _possibleConstructorReturn(this, _Component.apply(this, arguments));
    }

    WrappedComponent.prototype.render = function render() {
      var styles = void 0;

      var hasDefaultstyles = (0, _isObject3.default)(defaultStyles);
+      let renderResult;
      if (this.props.styles || hasDefaultstyles) {
        var props = Object.assign({}, this.props);

        if (props.styles) {
          styles = props.styles;
        } else if (hasDefaultstyles) {
          styles = defaultStyles;
          delete props.styles;
        }

        Object.defineProperty(props, 'styles', {
          configurable: true,
          enumerable: false,
          value: styles,
          writable: false
        });

-        this.props = props;
+        const originalProps = this.props;
+        try {
+          this.props = props;
+          renderResult = _Component.prototype.render.call(this);
+        } finally {
+          this.props = originalProps;
+        }
      } else {
        styles = {};
+        renderResult = _Component.prototype.render.call(this);
      }

-      var renderResult = _Component.prototype.render.call(this);

      if (renderResult) {
        return (0, _linkClass2.default)(renderResult, styles, options);
      }

      return (0, _renderNothing2.default)(_react2.default.version);
    };

    return WrappedComponent;
  }(Component);

  return (0, _hoistNonReactStatics2.default)(WrappedComponent, Component);
};

module.exports = exports['default']

I don't fully understand the intent of this change, though. This seems to just hide the error, rather than implementing a fix. Unfortunately, I am overloaded with projects far off React and as this project has been deprecated long time ago, I cannot allocate time to debug the issue.

I was just about to edit my message, I've tried the following but that didn't work (not quite sure if this would be the right way to test it)

1) Fork the react-css-modules repository and clone it locally
2) Make the changes in src/extendReactClass.js
3) Build it using npm run-script build
4) Link the package using npm link
5) Add the local npm package using npm install /usr/local/lib/node_modules/react-css-modules

I get the error props is not defined, this is now the source of my src/extendReactClass.js

/* eslint-disable react/prop-types */

import _ from 'lodash';
import React from 'react';
import hoistNonReactStatics from 'hoist-non-react-statics';
import linkClass from './linkClass';
import renderNothing from './renderNothing';

/**
 * @param {ReactClass} Component
 * @param {Object} defaultStyles
 * @param {Object} options
 * @returns {ReactClass}
 */
export default (Component: Object, defaultStyles: Object, options: Object) => {
  const WrappedComponent = class extends Component {
    render () {
      let styles;

      const hasDefaultstyles = _.isObject(defaultStyles);

      if (this.props.styles || hasDefaultstyles) {
        const props = Object.assign({}, this.props);

        if (props.styles) {
          styles = props.styles;
        } else if (hasDefaultstyles) {
          styles = defaultStyles;
          delete props.styles;
        }

        Object.defineProperty(props, 'styles', {
          configurable: true,
          enumerable: false,
          value: styles,
          writable: false
        });
      } else {
        styles = {};
      }

      const originalProps = this.props;

      try {
        this.props = props;
         const renderResult = super.render();
      } finally {
        this.props = originalProps;
      }

      if (renderResult) {
        return linkClass(renderResult, styles, options);
      }

      return renderNothing(React.version);
    }
  };

  return hoistNonReactStatics(WrappedComponent, Component);
};

I will now try the changes you suggested and will update this message accordingly.

EDIT:
@gajus I have tested the changes you suggested and the warnings do not appear anymore, also everything seems to be working fine again. I've tested it with react@^16.2.0 and react@^15.6.2, and found no issues.

Please let me know if there's anything I can do to finalize this change.

Thanks a lot @gajus, the new release works perfectly!

Edit: Thanks to you as well @gaearon :)

Thanks!

Was this page helpful?
0 / 5 - 0 ratings