Skip to content

Commit

Permalink
Shallow Renderer does not call componentDidUpdate when an update by s…
Browse files Browse the repository at this point in the history
…etState in v16
  • Loading branch information
koba04 authored and ljharb committed Sep 19, 2017
1 parent efb3913 commit d5373a3
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 3 deletions.
7 changes: 7 additions & 0 deletions packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ function nodeToHostNode(_node) {
}

class ReactSixteenAdapter extends EnzymeAdapter {
constructor() {
super();
this.options = {
...this.options,
enableComponentDidUpdateOnSetState: true,
};
}
createMountRenderer(options) {
assertDomAvailable('mount');
const domNode = options.attachTo || global.document.createElement('div');
Expand Down
1 change: 1 addition & 0 deletions packages/enzyme-test-suite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@
"react-dom": "^15.5.0"
}
}

3 changes: 1 addition & 2 deletions packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3441,8 +3441,7 @@ describe('shallow', () => {

context('updating state', () => {
// NOTE: There is a bug in react 16 shallow renderer where prevContext is not passed
// into componentDidUpdate. Skip this test for react 16 only. add back in if it gets fixed.
itIf(!REACT16, 'should call shouldComponentUpdate, componentWillUpdate and componentDidUpdate', () => {
it('should call shouldComponentUpdate, componentWillUpdate and componentDidUpdate', () => {
const spy = sinon.spy();

class Foo extends React.Component {
Expand Down
3 changes: 3 additions & 0 deletions packages/enzyme/src/EnzymeAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ function unimplementedError(methodName, classname) {
}

class EnzymeAdapter {
constructor() {
this.options = {};
}
// Provided a bag of options, return an `EnzymeRenderer`. Some options can be implementation
// specific, like `attach` etc. for React, but not part of this interface explicitly.
// eslint-disable-next-line class-methods-use-this, no-unused-vars
Expand Down
37 changes: 36 additions & 1 deletion packages/enzyme/src/ShallowWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,42 @@ class ShallowWrapper {
}
this.single('setState', () => {
withSetStateAllowed(() => {
this.instance().setState(state, callback);
const adapter = getAdapter(this[OPTIONS]);
const instance = this.instance();
const prevProps = instance.props;
const prevState = instance.state;
const prevContext = instance.context;
let shouldRender = true;
// This is a dirty hack but it requires to know the result of shouldComponentUpdate.
// When shouldComponentUpdate returns false we shouldn't call componentDidUpdate.
// shouldComponentUpdate is called in `instance.setState`
// so we replace shouldComponentUpdate to know the result and restore it later.
let originalShouldComponentUpdate;
if (
this[OPTIONS].lifecycleExperimental &&
adapter.options.enableComponentDidUpdateOnSetState &&
instance &&
typeof instance.shouldComponentUpdate === 'function'
) {
originalShouldComponentUpdate = instance.shouldComponentUpdate;
instance.shouldComponentUpdate = (...args) => {
shouldRender = originalShouldComponentUpdate.apply(instance, args);
return shouldRender;
};
}
instance.setState(state, callback);
if (
shouldRender &&
this[OPTIONS].lifecycleExperimental &&
adapter.options.enableComponentDidUpdateOnSetState &&
instance &&
typeof instance.componentDidUpdate === 'function'
) {
instance.componentDidUpdate(prevProps, prevState, prevContext);
}
if (originalShouldComponentUpdate) {
instance.shouldComponentUpdate = originalShouldComponentUpdate;
}
this.update();
});
});
Expand Down

0 comments on commit d5373a3

Please sign in to comment.