diff --git a/src/isomorphic/classic/class/ReactClass.js b/src/isomorphic/classic/class/ReactClass.js index 223e58bf63..8a2a6f30dd 100644 --- a/src/isomorphic/classic/class/ReactClass.js +++ b/src/isomorphic/classic/class/ReactClass.js @@ -710,7 +710,7 @@ var ReactClassMixin = { replaceState: function(newState, callback) { this.updater.enqueueReplaceState(this, newState); if (callback) { - this.updater.enqueueCallback(this, callback); + this.updater.enqueueCallback(this, callback, 'replaceState'); } }, diff --git a/src/isomorphic/modern/class/ReactComponent.js b/src/isomorphic/modern/class/ReactComponent.js index a7d8b4baa9..a775abd67d 100644 --- a/src/isomorphic/modern/class/ReactComponent.js +++ b/src/isomorphic/modern/class/ReactComponent.js @@ -76,7 +76,7 @@ ReactComponent.prototype.setState = function(partialState, callback) { } this.updater.enqueueSetState(this, partialState); if (callback) { - this.updater.enqueueCallback(this, callback); + this.updater.enqueueCallback(this, callback, 'setState'); } }; @@ -97,7 +97,7 @@ ReactComponent.prototype.setState = function(partialState, callback) { ReactComponent.prototype.forceUpdate = function(callback) { this.updater.enqueueForceUpdate(this); if (callback) { - this.updater.enqueueCallback(this, callback); + this.updater.enqueueCallback(this, callback, 'forceUpdate'); } }; diff --git a/src/renderers/shared/reconciler/ReactUpdateQueue.js b/src/renderers/shared/reconciler/ReactUpdateQueue.js index 5b5ccb368d..5ef49626f2 100644 --- a/src/renderers/shared/reconciler/ReactUpdateQueue.js +++ b/src/renderers/shared/reconciler/ReactUpdateQueue.js @@ -116,14 +116,15 @@ var ReactUpdateQueue = { * * @param {ReactClass} publicInstance The instance to use as `this` context. * @param {?function} callback Called after state is updated. + * @param {string} callerName Name of the calling function in the public API. * @internal */ - enqueueCallback: function(publicInstance, callback) { + enqueueCallback: function(publicInstance, callback, callerName) { invariant( typeof callback === 'function', - 'enqueueCallback(...): You called `setState`, `replaceState`, or ' + - '`forceUpdate` with the last argument of type %s. When specified, ' + - 'their last `callback` argument is expected to be a function.', + 'enqueueCallback(...): You called `%s` with the last argument of type ' + + '%s. When specified, its last `callback` argument must be a function.', + callerName, formatUnexpectedArgument(callback) ); var internalInstance = getInternalInstanceReadyForUpdate(publicInstance); diff --git a/src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js b/src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js index c217674c0e..4283e75c04 100644 --- a/src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js +++ b/src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js @@ -938,7 +938,7 @@ describe('ReactUpdates', function() { } }); - it('throws when the update callback is not a function', function() { + it('throws in setState if the update callback is not a function', function() { function Foo() { this.a = 1; this.b = 2; @@ -953,28 +953,84 @@ describe('ReactUpdates', function() { }); var component = ReactTestUtils.renderIntoDocument(); - var stringMessage = - 'enqueueCallback(...): You called `setState`, `replaceState`, or '+ - '`forceUpdate` with the last argument of type string. When specified, ' + - 'their last `callback` argument is expected to be a function.'; - expect(() => component.setState({}, 'no')).toThrow(stringMessage); - expect(() => component.replaceState({}, 'no')).toThrow(stringMessage); - expect(() => component.forceUpdate('no')).toThrow(stringMessage); + expect(() => component.setState({}, 'no')).toThrow( + 'enqueueCallback(...): You called `setState` with the last argument of ' + + 'type string. When specified, its last `callback` argument must be a ' + + 'function.' + ); + expect(() => component.setState({}, {})).toThrow( + 'enqueueCallback(...): You called `setState` with the last argument of ' + + 'type Object. When specified, its last `callback` argument must be a ' + + 'function.' + ); + expect(() => component.setState({}, new Foo())).toThrow( + 'enqueueCallback(...): You called `setState` with the last argument of ' + + 'type Foo (keys: a, b). When specified, its last `callback` argument ' + + 'must be a function.' + ); + }); - var objectMessage = - 'enqueueCallback(...): You called `setState`, `replaceState`, or '+ - '`forceUpdate` with the last argument of type Object. When specified, ' + - 'their last `callback` argument is expected to be a function.'; - expect(() => component.setState({}, {})).toThrow(objectMessage); - expect(() => component.replaceState({}, {})).toThrow(objectMessage); - expect(() => component.forceUpdate({})).toThrow(objectMessage); + it('throws in replaceState if the update callback is not a function', function() { + function Foo() { + this.a = 1; + this.b = 2; + } + var A = React.createClass({ + getInitialState: function() { + return {}; + }, + render: function() { + return
; + }, + }); + var component = ReactTestUtils.renderIntoDocument(); - var fooMessage = - 'enqueueCallback(...): You called `setState`, `replaceState`, or '+ - '`forceUpdate` with the last argument of type Foo (keys: a, b). When ' + - 'specified, their last `callback` argument is expected to be a function.'; - expect(() => component.setState({}, new Foo())).toThrow(fooMessage); - expect(() => component.replaceState({}, new Foo())).toThrow(fooMessage); - expect(() => component.forceUpdate(new Foo())).toThrow(fooMessage); + expect(() => component.replaceState({}, 'no')).toThrow( + 'enqueueCallback(...): You called `replaceState` with the last ' + + 'argument of type string. When specified, its last `callback` argument ' + + 'must be a function.' + ); + expect(() => component.replaceState({}, {})).toThrow( + 'enqueueCallback(...): You called `replaceState` with the last ' + + 'argument of type Object. When specified, its last `callback` argument ' + + 'must be a function.' + ); + expect(() => component.replaceState({}, new Foo())).toThrow( + 'enqueueCallback(...): You called `replaceState` with the last ' + + 'argument of type Foo (keys: a, b). When specified, its last ' + + '`callback` argument must be a function.' + ); + }); + + it('throws in forceUpdate if the update callback is not a function', function() { + function Foo() { + this.a = 1; + this.b = 2; + } + var A = React.createClass({ + getInitialState: function() { + return {}; + }, + render: function() { + return ; + }, + }); + var component = ReactTestUtils.renderIntoDocument(); + + expect(() => component.forceUpdate('no')).toThrow( + 'enqueueCallback(...): You called `forceUpdate` with the last ' + + 'argument of type string. When specified, its last `callback` argument ' + + 'must be a function.' + ); + expect(() => component.forceUpdate({})).toThrow( + 'enqueueCallback(...): You called `forceUpdate` with the last ' + + 'argument of type Object. When specified, its last `callback` argument ' + + 'must be a function.' + ); + expect(() => component.forceUpdate(new Foo())).toThrow( + 'enqueueCallback(...): You called `forceUpdate` with the last ' + + 'argument of type Foo (keys: a, b). When specified, its last ' + + '`callback` argument must be a function.' + ); }); });