@samitouri / QOS-React / commits / 552f00b536

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 });