-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Fix to call a setState callback after finishing the the render #1453
Fix to call a setState callback after finishing the the render #1453
Conversation
@@ -400,6 +402,10 @@ class ShallowWrapper { | |||
} | |||
} | |||
this.update(); | |||
// call the setState callback | |||
if (callback) { | |||
callback.call(instance); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are we sure that React calls the callback with zero arguments, and the instance as the receiver?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are we sure that React calls the callback with zero arguments, and the instance as the receiver?
I think so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That implementation doesn't show where update.callback
is assigned; but your demo is convincing (for the latest version of React).
However, in React ^15.4, it seems to get one argument set to undefined
in your demo (whereas in <= 15.3, and >= 16, it seems to get zero arguments). We should maintain that behavior via adapters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I'll work on to support < 15.3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tested for arguments of setState callback.
setState({foo: 'bar'}, function(...args) {
console.log(args);
});
- 0.13.3 ...
[]
- 0.14.9 ...
[]
- 15.4.2 ...
[ undefined ]
- 15.6.2 ...
[ undefined ]
- 16.2.0 ...
[]
The arguments seem to be [undefined]
only in v15.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's more than just painful; enzyme itself must not know about React at all, given that non-React adapters need to be able to work.
In other words, this functionality needs to be something that every adapter implements, and enzyme merely calls into.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enzyme itself must not know about React at all, given that non-React adapters need to be able to work.
I agree with you. But calling setState callback without argument is depending on current React implementation.
Spying setState callback and using the result later is to reproduce this functionality so I think it can be applied any adapters which are including non-React adapters.
instance.setState(state, spy);
:
if (callback) {
// replay setState callbacks
spy.getCalls().forEach((spyCall) => {
const context = spyCall.thisValue;
const args = spyCall.args;
callback.apply(context, args);
});
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your spy suggestion is interesting, but I was suggesting something like adapter.invokeSetStateCallback(instance, callback)
- I'm not sure which is better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I got it.
Are you ok to implement a default behavior into EnzymeAdapter
? If not, it will be a breaking change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think that would make sense.
279cbf6
to
d3c6cf1
Compare
@@ -49,4 +49,4 @@ | |||
"eslint-plugin-jsx-a11y": "^6.0.3", | |||
"eslint-plugin-react": "^7.5.1" | |||
} | |||
} | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's revert this file; every file should always have a trailing newline
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The diff is added again😉 I'm not digging into that but I guess it is added by a script to run tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, weird. maybe npm itself. not a big deal.
@@ -385,7 +385,9 @@ class ShallowWrapper { | |||
return shouldRender; | |||
}; | |||
} | |||
instance.setState(state, callback); | |||
// We don't pass the setState callback here | |||
// to guarantee to call the callback after finishing the render |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add a test case for this? or does expect(wrapper.find('div').prop('className')).to.eql('bar');
cover that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, expect(wrapper.find('div').prop('className')).to.eql('bar');
is a test for this.
40540e4
to
4afbef7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, just some questions
|
||
// The shallow renderer in react 16 does not yet support batched updates. When it does, | ||
// we should be able to go un-skip all of the tests that are skipped with this flag. | ||
const BATCHING = !REACT16; | ||
|
||
|
||
// some React versions pass undefined as an argument of setState callback. | ||
const CALLING_SETSTATE_CALLBACK_WITH_UNDEFINED = REACT154 || REACT155 || REACT156; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could this be made a bit more robust by changing it to !REACT15 && !REACT150 && !REACT151 && !REACT152 && !REACT153
, since those are the only 4 versions of 15 that will ever not have this behavior?
if (MINOR_VERSION >= 4) { | ||
callback.call(instance, undefined); | ||
} else { | ||
callback.call(instance); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe this could do super.invokeSetStateCallback(instance, callback)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
Thank you for your review! |
Tests seem to be failing in React 15, both locally and in travis. I'm going to see what I can do to figure it out now. |
6ea171e
to
751b1f7
Compare
'^15.4', | ||
() => { callback.call(instance, undefined); }, | ||
() => { super.invokeSetStateCallback(instance, callback); }, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ifReact
returns a function, not calling it so you have to call a function which is returned from ifReact
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol, oops. shouldn't tests have failed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix in a followup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so we should add a test to guarantee to call a setState callback.
Currently, we don't have a test for it.
|
||
// The shallow renderer in react 16 does not yet support batched updates. When it does, | ||
// we should be able to go un-skip all of the tests that are skipped with this flag. | ||
const BATCHING = !REACT16; | ||
|
||
// some React versions pass undefined as an argument of setState callback. | ||
const CALLING_SETSTATE_CALLBACK_WITH_UNDEFINED = REACT15 && !REACT150_4; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this condition doesn't cover a case that React v15.4 calls setState callback with undefined
.
But all tests are passing in my environment. The reason is ifReact
is checking with React v16.2 because enzyme-adapter-react-helper
has React v16.2 into its node_modules
.
I'm not sure why enzyme-adapter-react-helper
has React v16.2 into its node_modules
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it has a peer dep/dev dep on react >= 0.13, so that'd install the latest, but i'm not sure why that would do it. let me try something tho
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I'll dig into it later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enzyme-adapter-react-helper
shouldn't have react
as a devDependencies?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since it's a peer dep, it also needs to be a dev dep.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other packages don't have react as a devDeps. Why does only this package have it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a bug leftover from the v3 migration; they all should - every peer dep in every npm package anywhere must always have the identical peer dep string as a dep or a dev dep.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've almost got a fix PR with a regression test; i'll put it up shortly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- [fix] actually invoke the setState callback (#1465, #1453) - [fix] add missing support for animation events (#1569) - [fix] call ref for a root element (#1541) - [fix] Allow empty strings as key props (#1524) - [fix] correct the adapter class name - [fix] `mount`: make sure it works with native arrow functions (#1667) - [refactor]: add “lifecycles” adapter option (#1696) - [refactor] use `react-is` package - [deps] remove unneeded `lodash` dep; update `prop-types`, `enzyme-adapter-utils`, `object.assign` - [dev deps] update `babel-cli`, `babel-core`, `babel-plugin-transform-replace-object-assign`, `babel-preset-airbnb`, `chai`, `coveralls`, `eslint`, `eslint-config-airbnb`, `eslint-plugin-import`, `eslint-plugin-jsx-a11y`, `eslint-plugin-react`, `lerna`, `json-loader`, `karma-firefox-launcher`, `prop-types`, `lerna`, `rimraf`, `webpack`
- [fix] actually invoke the setState callback (#1465, #1453) - [fix] add missing support for animation events (#1569) - [fix] call ref for a root element (#1541) - [fix] Allow empty strings as key props (#1524) - [fix] correct the adapter class name - [fix] `mount`: make sure it works with native arrow functions (#1667) - [refactor]: add “lifecycles” adapter option (#1696) - [refactor] use `react-is` package - [deps] remove unneeded `lodash` dep; update `prop-types`, `enzyme-adapter-utils`, `object.assign` - [dev deps] update `babel-cli`, `babel-core`, `babel-plugin-transform-replace-object-assign`, `babel-preset-airbnb`, `chai`, `coveralls`, `eslint`, `eslint-config-airbnb`, `eslint-plugin-import`, `eslint-plugin-jsx-a11y`, `eslint-plugin-react`, `lerna`, `json-loader`, `karma-firefox-launcher`, `prop-types`, `lerna`, `rimraf`, `webpack`
This PR is to fix #1451.
Currently, a setState callback is called before finishing the render because enzyme is passing the callback into
instance.setState
.So I've fixed it to call the callback after
this.update()