Make setState() callback error message more descriptive
Fixes #6306
Dan Abramov committed
Mar 21, 2016 at 19:10 UTC
552f00b536b0f3edc520dd1bc09132b9b0f87e8d
2 files changed
+61
-10
src/renderers/shared/reconciler/ReactUpdateQueue.js
+21
-10
@@ -22,6 +22,19 @@ function enqueueUpdate(internalInstance) {
22
ReactUpdates.enqueueUpdate(internalInstance);
23
}
24
25
+function formatUnexpectedArgument(arg) {
26
+ var type = typeof arg;
27
+ if (type !== 'object') {
28
+ return type;
29
+ }
30
+ var displayName = arg.constructor && arg.constructor.name || type;
31
+ var keys = Object.keys(arg);
32
+ if (keys.length > 0 && keys.length < 20) {
33
+ return `${displayName} (keys: ${keys.join(', ')})`;
34
+ }
35
+ return displayName;
36
+}
37
+
38
function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
39
var internalInstance = ReactInstanceMap.get(publicInstance);
40
if (!internalInstance) {
@@ -108,11 +121,10 @@ var ReactUpdateQueue = {
121
enqueueCallback: function(publicInstance, callback) {
122
invariant(
123
typeof callback === 'function',
111
- 'enqueueCallback(...): You called `setProps`, `replaceProps`, ' +
112
- '`setState`, `replaceState`, or `forceUpdate` with a callback of type ' +
113
- '%s. A function is expected',
114
- typeof callback === 'object' && Object.keys(callback).length && Object.keys(callback).length < 20 ?
115
- typeof callback + ' (keys: ' + Object.keys(callback) + ')' : typeof callback
124
+ 'enqueueCallback(...): You called `setState`, `replaceState`, or ' +
125
+ '`forceUpdate` with the last argument of type %s. When specified, ' +
126
+ 'their last `callback` argument is expected to be a function.',
127
+ formatUnexpectedArgument(callback)
128
);
129
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
130
@@ -140,11 +152,10 @@ var ReactUpdateQueue = {
152
enqueueCallbackInternal: function(internalInstance, callback) {
153
invariant(
154
typeof callback === 'function',
143
- 'enqueueCallback(...): You called `setProps`, `replaceProps`, ' +
144
- '`setState`, `replaceState`, or `forceUpdate` with a callback of type ' +
145
- '%s. A function is expected',
146
- typeof callback === 'object' && Object.keys(callback).length && Object.keys(callback).length < 20 ?
147
- typeof callback + ' (keys: ' + Object.keys(callback) + ')' : typeof callback
155
+ 'enqueueCallback(...): You called `setState`, `replaceState`, or ' +
156
+ '`forceUpdate` with the last argument of type %s. When specified, ' +
157
+ 'their last `callback` argument is expected to be a function.',
158
+ formatUnexpectedArgument(callback)
159
);
160
if (internalInstance._pendingCallbacks) {
161
internalInstance._pendingCallbacks.push(callback);
src/renderers/shared/reconciler/__tests__/ReactUpdates-test.js
+40
@@ -937,4 +937,44 @@ describe('ReactUpdates', function() {
937
ReactFeatureFlags.logTopLevelRenders = false;
938
}
939
});
940
+
941
+ it('throws when the update callback is not a function', function() {
942
+ function Foo() {
943
+ this.a = 1;
944
+ this.b = 2;
945
+ }
946
+ var A = React.createClass({
947
+ getInitialState: function() {
948
+ return {};
949
+ },
950
+ render: function() {
951
+ return <div />;
952
+ },
953
+ });
954
+ var component = ReactTestUtils.renderIntoDocument(<A />);
955
+
956
+ var stringMessage =
957
+ 'enqueueCallback(...): You called `setState`, `replaceState`, or '+
958
+ '`forceUpdate` with the last argument of type string. When specified, ' +
959
+ 'their last `callback` argument is expected to be a function.';
960
+ expect(() => component.setState({}, 'no')).toThrow(stringMessage);
961
+ expect(() => component.replaceState({}, 'no')).toThrow(stringMessage);
962
+ expect(() => component.forceUpdate('no')).toThrow(stringMessage);
963
+
964
+ var objectMessage =
965
+ 'enqueueCallback(...): You called `setState`, `replaceState`, or '+
966
+ '`forceUpdate` with the last argument of type Object. When specified, ' +
967
+ 'their last `callback` argument is expected to be a function.';
968
+ expect(() => component.setState({}, {})).toThrow(objectMessage);
969
+ expect(() => component.replaceState({}, {})).toThrow(objectMessage);
970
+ expect(() => component.forceUpdate({})).toThrow(objectMessage);
971
+
972
+ var fooMessage =
973
+ 'enqueueCallback(...): You called `setState`, `replaceState`, or '+
974
+ '`forceUpdate` with the last argument of type Foo (keys: a, b). When ' +
975
+ 'specified, their last `callback` argument is expected to be a function.';
976
+ expect(() => component.setState({}, new Foo())).toThrow(fooMessage);
977
+ expect(() => component.replaceState({}, new Foo())).toThrow(fooMessage);
978
+ expect(() => component.forceUpdate(new Foo())).toThrow(fooMessage);
979
+ });
980
});