From 552f00b536b0f3edc520dd1bc09132b9b0f87e8d Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Mon, 21 Mar 2016 19:10:46 +0000 Subject: [PATCH 1/4] Make setState() callback error message more descriptive Fixes #6306 --- .../shared/reconciler/ReactUpdateQueue.js | 31 +++++++++----- .../reconciler/__tests__/ReactUpdates-test.js | 40 +++++++++++++++++++ 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/src/renderers/shared/reconciler/ReactUpdateQueue.js b/src/renderers/shared/reconciler/ReactUpdateQueue.js index d2e7cadb28cf..5b5ccb368d83 100644 --- a/src/renderers/shared/reconciler/ReactUpdateQueue.js +++ b/src/renderers/shared/reconciler/ReactUpdateQueue.js @@ -22,6 +22,19 @@ function enqueueUpdate(internalInstance) { ReactUpdates.enqueueUpdate(internalInstance); } +function formatUnexpectedArgument(arg) { + var type = typeof arg; + if (type !== 'object') { + return type; + } + var displayName = arg.constructor && arg.constructor.name || type; + var keys = Object.keys(arg); + if (keys.length > 0 && keys.length < 20) { + return `${displayName} (keys: ${keys.join(', ')})`; + } + return displayName; +} + function getInternalInstanceReadyForUpdate(publicInstance, callerName) { var internalInstance = ReactInstanceMap.get(publicInstance); if (!internalInstance) { @@ -108,11 +121,10 @@ var ReactUpdateQueue = { enqueueCallback: function(publicInstance, callback) { invariant( typeof callback === 'function', - 'enqueueCallback(...): You called `setProps`, `replaceProps`, ' + - '`setState`, `replaceState`, or `forceUpdate` with a callback of type ' + - '%s. A function is expected', - typeof callback === 'object' && Object.keys(callback).length && Object.keys(callback).length < 20 ? - typeof callback + ' (keys: ' + Object.keys(callback) + ')' : typeof callback + '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.', + formatUnexpectedArgument(callback) ); var internalInstance = getInternalInstanceReadyForUpdate(publicInstance); @@ -140,11 +152,10 @@ var ReactUpdateQueue = { enqueueCallbackInternal: function(internalInstance, callback) { invariant( typeof callback === 'function', - 'enqueueCallback(...): You called `setProps`, `replaceProps`, ' + - '`setState`, `replaceState`, or `forceUpdate` with a callback of type ' + - '%s. A function is expected', - typeof callback === 'object' && Object.keys(callback).length && Object.keys(callback).length < 20 ? - typeof callback + ' (keys: ' + Object.keys(callback) + ')' : typeof callback + '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.', + formatUnexpectedArgument(callback) ); if (internalInstance._pendingCallbacks) { internalInstance._pendingCallbacks.push(callback); diff --git a/src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js b/src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js index 22dddac8be58..c217674c0e33 100644 --- a/src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js +++ b/src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js @@ -937,4 +937,44 @@ describe('ReactUpdates', function() { ReactFeatureFlags.logTopLevelRenders = false; } }); + + it('throws when 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 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); + + 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); + + 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); + }); }); From ebe51284896981142af5b8b1509df17419f2b644 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Mon, 21 Mar 2016 21:40:17 +0000 Subject: [PATCH 2/4] Use specific method names in error messages --- src/isomorphic/classic/class/ReactClass.js | 2 +- src/isomorphic/modern/class/ReactComponent.js | 4 +- .../shared/reconciler/ReactUpdateQueue.js | 9 +- .../reconciler/__tests__/ReactUpdates-test.js | 104 ++++++++++++++---- 4 files changed, 88 insertions(+), 31 deletions(-) diff --git a/src/isomorphic/classic/class/ReactClass.js b/src/isomorphic/classic/class/ReactClass.js index 223e58bf6318..8a2a6f30dd51 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 a7d8b4baa92c..a775abd67dda 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 5b5ccb368d83..5ef49626f2be 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 c217674c0e33..4283e75c0407 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); - - 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); - - 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.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.' + ); + }); + + 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(); + + 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.' + ); }); }); From d4657b83318d59b837c04c9c4b112aa0793d8b3a Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Mon, 21 Mar 2016 22:31:44 +0000 Subject: [PATCH 3/4] Add bad callback invariants to ReactDOM.render() calls --- src/renderers/dom/client/ReactMount.js | 1 + .../dom/client/__tests__/ReactDOM-test.js | 59 +++++++++++++++++++ .../shared/reconciler/ReactUpdateQueue.js | 25 ++++---- .../reconciler/__tests__/ReactUpdates-test.js | 45 ++++++-------- 4 files changed, 89 insertions(+), 41 deletions(-) diff --git a/src/renderers/dom/client/ReactMount.js b/src/renderers/dom/client/ReactMount.js index ac6e6841b099..7f6f8cac6ff5 100644 --- a/src/renderers/dom/client/ReactMount.js +++ b/src/renderers/dom/client/ReactMount.js @@ -382,6 +382,7 @@ var ReactMount = { }, _renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) { + ReactUpdateQueue.validateCallback(callback, 'ReactDOM.render'); invariant( ReactElement.isValidElement(nextElement), 'ReactDOM.render(): Invalid component element.%s', diff --git a/src/renderers/dom/client/__tests__/ReactDOM-test.js b/src/renderers/dom/client/__tests__/ReactDOM-test.js index b55381f1d499..cd1d7495d9c4 100644 --- a/src/renderers/dom/client/__tests__/ReactDOM-test.js +++ b/src/renderers/dom/client/__tests__/ReactDOM-test.js @@ -118,4 +118,63 @@ describe('ReactDOM', function() { expect(console.error.argsForCall.length).toBe(0); }); + it('throws in render() if the mount 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 myDiv = document.createElement('div'); + expect(() => ReactDOM.render(, myDiv, 'no')).toThrow( + 'You called `ReactDOM.render` with the last argument of type string. ' + + 'When specified, its last `callback` argument must be a function.' + ); + expect(() => ReactDOM.render(, myDiv, {})).toThrow( + 'You called `ReactDOM.render` with the last argument of type Object. ' + + 'When specified, its last `callback` argument must be a function.' + ); + expect(() => ReactDOM.render(, myDiv, new Foo())).toThrow( + 'You called `ReactDOM.render` with the last argument of type Foo (keys: a, b). ' + + 'When specified, its last `callback` argument must be a function.' + ); + }); + + it('throws in render() 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 myDiv = document.createElement('div'); + ReactDOM.render(, myDiv); + + expect(() => ReactDOM.render(, myDiv, 'no')).toThrow( + 'You called `ReactDOM.render` with the last argument of type string. ' + + 'When specified, its last `callback` argument must be a function.' + ); + expect(() => ReactDOM.render(, myDiv, {})).toThrow( + 'You called `ReactDOM.render` with the last argument of type Object. ' + + 'When specified, its last `callback` argument must be a function.' + ); + expect(() => ReactDOM.render(, myDiv, new Foo())).toThrow( + 'You called `ReactDOM.render` with the last argument of type Foo (keys: a, b). ' + + 'When specified, its last `callback` argument must be a function.' + ); + }); }); diff --git a/src/renderers/shared/reconciler/ReactUpdateQueue.js b/src/renderers/shared/reconciler/ReactUpdateQueue.js index 5ef49626f2be..3d2f7e7f6fe9 100644 --- a/src/renderers/shared/reconciler/ReactUpdateQueue.js +++ b/src/renderers/shared/reconciler/ReactUpdateQueue.js @@ -120,13 +120,7 @@ var ReactUpdateQueue = { * @internal */ enqueueCallback: function(publicInstance, callback, callerName) { - invariant( - typeof callback === '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) - ); + ReactUpdateQueue.validateCallback(callback, callerName); var internalInstance = getInternalInstanceReadyForUpdate(publicInstance); // Previously we would throw an error if we didn't have an internal @@ -151,13 +145,6 @@ var ReactUpdateQueue = { }, enqueueCallbackInternal: function(internalInstance, callback) { - 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.', - formatUnexpectedArgument(callback) - ); if (internalInstance._pendingCallbacks) { internalInstance._pendingCallbacks.push(callback); } else { @@ -254,6 +241,16 @@ var ReactUpdateQueue = { enqueueUpdate(internalInstance); }, + validateCallback: function(callback, callerName) { + invariant( + !callback || typeof callback === 'function', + 'You called `%s` with the last argument of type %s. ' + + 'When specified, its last `callback` argument must be a function.', + callerName, + formatUnexpectedArgument(callback) + ); + }, + }; module.exports = ReactUpdateQueue; diff --git a/src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js b/src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js index 4283e75c0407..3f548fda9b6f 100644 --- a/src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js +++ b/src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js @@ -954,19 +954,16 @@ describe('ReactUpdates', function() { var component = ReactTestUtils.renderIntoDocument(); 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.' + '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.' + '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.' + 'You called `setState` with the last argument of type Foo (keys: a, b). ' + + 'When specified, its last `callback` argument must be a function.' ); }); @@ -986,19 +983,16 @@ describe('ReactUpdates', function() { var component = ReactTestUtils.renderIntoDocument(); 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.' + '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.' + '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.' + 'You called `replaceState` with the last argument of type Foo (keys: a, b). ' + + 'When specified, its last `callback` argument must be a function.' ); }); @@ -1018,19 +1012,16 @@ describe('ReactUpdates', function() { 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.' + '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.' + '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.' + 'You called `forceUpdate` with the last argument of type Foo (keys: a, b). ' + + 'When specified, its last `callback` argument must be a function.' ); }); }); From af4fe68b145e4a11c868e8bd81494138b38b27e4 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Mon, 21 Mar 2016 23:13:16 +0000 Subject: [PATCH 4/4] Change message format to be more consistent with other errors --- .../dom/client/__tests__/ReactDOM-test.js | 24 ++++++------- .../shared/reconciler/ReactUpdateQueue.js | 4 +-- .../reconciler/__tests__/ReactUpdates-test.js | 36 +++++++++---------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/renderers/dom/client/__tests__/ReactDOM-test.js b/src/renderers/dom/client/__tests__/ReactDOM-test.js index cd1d7495d9c4..9c3cef57f90d 100644 --- a/src/renderers/dom/client/__tests__/ReactDOM-test.js +++ b/src/renderers/dom/client/__tests__/ReactDOM-test.js @@ -134,16 +134,16 @@ describe('ReactDOM', function() { var myDiv = document.createElement('div'); expect(() => ReactDOM.render(, myDiv, 'no')).toThrow( - 'You called `ReactDOM.render` with the last argument of type string. ' + - 'When specified, its last `callback` argument must be a function.' + 'ReactDOM.render(...): Expected the last optional `callback` argument ' + + 'to be a function. Instead received: string.' ); expect(() => ReactDOM.render(, myDiv, {})).toThrow( - 'You called `ReactDOM.render` with the last argument of type Object. ' + - 'When specified, its last `callback` argument must be a function.' + 'ReactDOM.render(...): Expected the last optional `callback` argument ' + + 'to be a function. Instead received: Object.' ); expect(() => ReactDOM.render(, myDiv, new Foo())).toThrow( - 'You called `ReactDOM.render` with the last argument of type Foo (keys: a, b). ' + - 'When specified, its last `callback` argument must be a function.' + 'ReactDOM.render(...): Expected the last optional `callback` argument ' + + 'to be a function. Instead received: Foo (keys: a, b).' ); }); @@ -165,16 +165,16 @@ describe('ReactDOM', function() { ReactDOM.render(, myDiv); expect(() => ReactDOM.render(, myDiv, 'no')).toThrow( - 'You called `ReactDOM.render` with the last argument of type string. ' + - 'When specified, its last `callback` argument must be a function.' + 'ReactDOM.render(...): Expected the last optional `callback` argument ' + + 'to be a function. Instead received: string.' ); expect(() => ReactDOM.render(, myDiv, {})).toThrow( - 'You called `ReactDOM.render` with the last argument of type Object. ' + - 'When specified, its last `callback` argument must be a function.' + 'ReactDOM.render(...): Expected the last optional `callback` argument ' + + 'to be a function. Instead received: Object.' ); expect(() => ReactDOM.render(, myDiv, new Foo())).toThrow( - 'You called `ReactDOM.render` with the last argument of type Foo (keys: a, b). ' + - 'When specified, its last `callback` argument must be a function.' + 'ReactDOM.render(...): Expected the last optional `callback` argument ' + + 'to be a function. Instead received: Foo (keys: a, b).' ); }); }); diff --git a/src/renderers/shared/reconciler/ReactUpdateQueue.js b/src/renderers/shared/reconciler/ReactUpdateQueue.js index 3d2f7e7f6fe9..65bdd490525a 100644 --- a/src/renderers/shared/reconciler/ReactUpdateQueue.js +++ b/src/renderers/shared/reconciler/ReactUpdateQueue.js @@ -244,8 +244,8 @@ var ReactUpdateQueue = { validateCallback: function(callback, callerName) { invariant( !callback || typeof callback === 'function', - 'You called `%s` with the last argument of type %s. ' + - 'When specified, its last `callback` argument must be a function.', + '%s(...): Expected the last optional `callback` argument to be a ' + + 'function. Instead received: %s.', callerName, formatUnexpectedArgument(callback) ); diff --git a/src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js b/src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js index 3f548fda9b6f..f59193d691ca 100644 --- a/src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js +++ b/src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js @@ -954,16 +954,16 @@ describe('ReactUpdates', function() { var component = ReactTestUtils.renderIntoDocument(); expect(() => component.setState({}, 'no')).toThrow( - 'You called `setState` with the last argument of type string. ' + - 'When specified, its last `callback` argument must be a function.' + 'setState(...): Expected the last optional `callback` argument ' + + 'to be a function. Instead received: string.' ); expect(() => component.setState({}, {})).toThrow( - 'You called `setState` with the last argument of type Object. ' + - 'When specified, its last `callback` argument must be a function.' + 'setState(...): Expected the last optional `callback` argument ' + + 'to be a function. Instead received: Object.' ); expect(() => component.setState({}, new Foo())).toThrow( - 'You called `setState` with the last argument of type Foo (keys: a, b). ' + - 'When specified, its last `callback` argument must be a function.' + 'setState(...): Expected the last optional `callback` argument ' + + 'to be a function. Instead received: Foo (keys: a, b).' ); }); @@ -983,16 +983,16 @@ describe('ReactUpdates', function() { var component = ReactTestUtils.renderIntoDocument(); expect(() => component.replaceState({}, 'no')).toThrow( - 'You called `replaceState` with the last argument of type string. ' + - 'When specified, its last `callback` argument must be a function.' + 'replaceState(...): Expected the last optional `callback` argument ' + + 'to be a function. Instead received: string.' ); expect(() => component.replaceState({}, {})).toThrow( - 'You called `replaceState` with the last argument of type Object. ' + - 'When specified, its last `callback` argument must be a function.' + 'replaceState(...): Expected the last optional `callback` argument ' + + 'to be a function. Instead received: Object.' ); expect(() => component.replaceState({}, new Foo())).toThrow( - 'You called `replaceState` with the last argument of type Foo (keys: a, b). ' + - 'When specified, its last `callback` argument must be a function.' + 'replaceState(...): Expected the last optional `callback` argument ' + + 'to be a function. Instead received: Foo (keys: a, b).' ); }); @@ -1012,16 +1012,16 @@ describe('ReactUpdates', function() { var component = ReactTestUtils.renderIntoDocument(); expect(() => component.forceUpdate('no')).toThrow( - 'You called `forceUpdate` with the last argument of type string. ' + - 'When specified, its last `callback` argument must be a function.' + 'forceUpdate(...): Expected the last optional `callback` argument ' + + 'to be a function. Instead received: string.' ); expect(() => component.forceUpdate({})).toThrow( - 'You called `forceUpdate` with the last argument of type Object. ' + - 'When specified, its last `callback` argument must be a function.' + 'forceUpdate(...): Expected the last optional `callback` argument ' + + 'to be a function. Instead received: Object.' ); expect(() => component.forceUpdate(new Foo())).toThrow( - 'You called `forceUpdate` with the last argument of type Foo (keys: a, b). ' + - 'When specified, its last `callback` argument must be a function.' + 'forceUpdate(...): Expected the last optional `callback` argument ' + + 'to be a function. Instead received: Foo (keys: a, b).' ); }); });