react-test-renderer/shallow setState callback is never called

Created on 1 Jul 2017  路  15Comments  路  Source: facebook/react

Do you want to request a feature or report a bug?

Bug, following on from https://github.com/airbnb/enzyme/issues/953

What is the current behavior?

In node_modules/react-test-renderer/cjs/react-test-renderer-shallow.development.js, the callback second argument to setState is never called.

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://jsfiddle.net or similar (template: https://jsfiddle.net/84v837e9/).

import ShallowRenderer from 'react-test-renderer/shallow'

const renderer = new ShallowRenderer()
renderer.render(<YourComponent />)
const instance = renderer.getMountedInstance()
instance.methodThatCallsSetState()
// => callback passed into setState as the second argument within methodThatCallsSetState is never called

The shallow renderer code is as follows:

Updater.prototype.enqueueSetState = function enqueueSetState(publicInstance, partialState, callback, callerName) {
  if (typeof partialState === 'function') {
    partialState = partialState(publicInstance.state, publicInstance.props);
  }

  this._renderer._newState = _extends({}, publicInstance.state, partialState);

  this._renderer.render(this._renderer._element, this._renderer._context);
};

As you can see, the callback is ignored. In the deep renderer (node_modules/react-test-renderer/cjs/react-test-renderer.development.js), the callback _is_ used:

enqueueSetState: function (instance, partialState, callback) {
  var fiber = ReactInstanceMap_1.get(instance);
  var priorityLevel = getPriorityContext(fiber, false);
  callback = callback === undefined ? null : callback;
  {
    warnOnInvalidCallback(callback, 'setState');
  }
  addUpdate$1(fiber, partialState, callback, priorityLevel);
  scheduleUpdate(fiber, priorityLevel);
}

What is the expected behavior?

The callback is called.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

React v16, react-test-renderer v16. I don't know if this worked before.

Bug

All 15 comments

Would you be interested in submitting a failing test case as a PR?

cc @bvaughn

Yes absolutely. Will get to it later today, might need some help, I'll ask on here if so!

Hmm ok I have no idea where my test should be. I can't even find the shallow-test-renderer code 馃槙

Search for existing tests importing shallow renderer? I bet it's called something like ReactShallowRenderer-test.

the callback second argument to setState is never called.

Looks correct, unfortunately. The ReactShallowRendererEntry implementation doesn't use the callback param at all .

@Leeds-eBooks You're looking for ReactTestUtils-test.

Please feel free to assign the PR with this failing test to me. I can review and fix.

@bvaughn Never contributed to react before. Mind if I take a stab at it? Seems straightforward.

Sounds great!

Should the callback bind a public instance of a component to this as other renderers(reconciler) do?

Do we do that elsewhere? I think we only do it when we explicitly pass a function references around in such a way as to cause it to lose its context. I'm not very familiar with that code to be honest.

I wouldn't expect the callback to be auto-bound by react.

I think the ReactMount example is different, because there's no instance to pre-bind to. (It doesn't exist until after you've called render.) In the setState case you have a variety of tools to manage your own bindings (lexical scope, fat-arrow functions, .bind(this)).

I'm not seeing what you describe in ReactUpdateQueue. We aren't binding a this to the callback. We're just queueing up the callback to be called later. (That "later" is part of the code that I'm not very familiar with.)

@bvaughn
My comment was a bit confusing.
What I suggest at the comment is to support the following case.

instance.setState({x: 2}, function() {
  expect(this).toBe(instance);
});

https://github.com/facebook/react/blob/master/src/renderers/__tests__/ReactUpdates-test.js#L243

The callback doesn't bind this, but it is called with function.prototype.call so that this becomes a public instance.

That makes sense! 馃槃

Yep, I just figured that out, from ReactFiberCommitWork and ReactFiberUpdateQueue

I'll add tests! :)

@koba04 @bvaughn All done! See #10106

Was this page helpful?
0 / 5 - 0 ratings