diff --git a/src/renderers/shared/reconciler/ReactUpdateQueue.js b/src/renderers/shared/reconciler/ReactUpdateQueue.js index d2e7cadb28..5b5ccb368d 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 22dddac8be..c217674c0e 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); + }); });