Skip to content

Commit bc05ef1

Browse files
author
Brian Vaughn
committed
Add callback validation to fiber-based renderers
Moved ReactFiberClassComponent validateCallback() helper function into a standalone util used by both fiber and stack implementations. Validation now happens in ReactFiberUpdateQueue so that non-DOM renderers will also benefit from it.
1 parent 0402106 commit bc05ef1

File tree

7 files changed

+59
-86
lines changed

7 files changed

+59
-86
lines changed

scripts/fiber/tests-failing.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ src/isomorphic/classic/__tests__/ReactContextValidator-test.js
1212
src/renderers/dom/__tests__/ReactDOMProduction-test.js
1313
* should throw with an error code in production
1414

15-
src/renderers/dom/shared/__tests__/ReactDOM-test.js
16-
* throws in render() if the mount callback is not a function
17-
* throws in render() if the update callback is not a function
18-
1915
src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
2016
* should clean up input value tracking
2117
* should clean up input textarea tracking

scripts/fiber/tests-passing.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,8 @@ src/renderers/dom/shared/__tests__/ReactDOM-test.js
620620
* should overwrite props.children with children argument
621621
* should purge the DOM cache when removing nodes
622622
* allow React.DOM factories to be called without warnings
623+
* throws in render() if the mount callback is not a function
624+
* throws in render() if the update callback is not a function
623625
* preserves focus
624626
* calls focus() on autoFocus elements after they have been mounted to the DOM
625627

src/renderers/dom/shared/__tests__/ReactDOM-test.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,15 +133,15 @@ describe('ReactDOM', () => {
133133

134134
var myDiv = document.createElement('div');
135135
expect(() => ReactDOM.render(<A />, myDiv, 'no')).toThrowError(
136-
'ReactDOM.render(...): Expected the last optional `callback` argument ' +
136+
'render(...): Expected the last optional `callback` argument ' +
137137
'to be a function. Instead received: string.'
138138
);
139139
expect(() => ReactDOM.render(<A />, myDiv, {})).toThrowError(
140-
'ReactDOM.render(...): Expected the last optional `callback` argument ' +
140+
'render(...): Expected the last optional `callback` argument ' +
141141
'to be a function. Instead received: Object.'
142142
);
143143
expect(() => ReactDOM.render(<A />, myDiv, new Foo())).toThrowError(
144-
'ReactDOM.render(...): Expected the last optional `callback` argument ' +
144+
'render(...): Expected the last optional `callback` argument ' +
145145
'to be a function. Instead received: Foo (keys: a, b).'
146146
);
147147
});
@@ -164,15 +164,15 @@ describe('ReactDOM', () => {
164164
ReactDOM.render(<A />, myDiv);
165165

166166
expect(() => ReactDOM.render(<A />, myDiv, 'no')).toThrowError(
167-
'ReactDOM.render(...): Expected the last optional `callback` argument ' +
167+
'render(...): Expected the last optional `callback` argument ' +
168168
'to be a function. Instead received: string.'
169169
);
170170
expect(() => ReactDOM.render(<A />, myDiv, {})).toThrowError(
171-
'ReactDOM.render(...): Expected the last optional `callback` argument ' +
171+
'render(...): Expected the last optional `callback` argument ' +
172172
'to be a function. Instead received: Object.'
173173
);
174174
expect(() => ReactDOM.render(<A />, myDiv, new Foo())).toThrowError(
175-
'ReactDOM.render(...): Expected the last optional `callback` argument ' +
175+
'render(...): Expected the last optional `callback` argument ' +
176176
'to be a function. Instead received: Foo (keys: a, b).'
177177
);
178178
});

src/renderers/shared/fiber/ReactFiberClassComponent.js

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -33,31 +33,6 @@ var invariant = require('invariant');
3333

3434
const isArray = Array.isArray;
3535

36-
function formatUnexpectedArgument(arg) {
37-
var type = typeof arg;
38-
if (type !== 'object') {
39-
return type;
40-
}
41-
var displayName = arg.constructor && arg.constructor.name || type;
42-
var keys = Object.keys(arg);
43-
if (keys.length > 0 && keys.length < 20) {
44-
return `${displayName} (keys: ${keys.join(', ')})`;
45-
}
46-
return displayName;
47-
}
48-
49-
function validateCallback(callback, callerName) {
50-
if (typeof callback !== 'function') {
51-
invariant(
52-
false,
53-
'%s(...): Expected the last optional `callback` argument to be a ' +
54-
'function. Instead received: %s.',
55-
callerName,
56-
formatUnexpectedArgument(callback)
57-
);
58-
}
59-
}
60-
6136
module.exports = function(
6237
scheduleUpdate : (fiber : Fiber, priorityLevel : PriorityLevel) => void,
6338
getPriorityContext : () => PriorityLevel,
@@ -67,27 +42,18 @@ module.exports = function(
6742
const updater = {
6843
isMounted,
6944
enqueueSetState(instance, partialState, callback) {
70-
if (callback) {
71-
validateCallback(callback, 'setState');
72-
}
7345
const fiber = ReactInstanceMap.get(instance);
7446
const priorityLevel = getPriorityContext();
7547
addUpdate(fiber, partialState, callback || null, priorityLevel);
7648
scheduleUpdate(fiber, priorityLevel);
7749
},
7850
enqueueReplaceState(instance, state, callback) {
79-
if (callback) {
80-
validateCallback(callback, 'replaceState');
81-
}
8251
const fiber = ReactInstanceMap.get(instance);
8352
const priorityLevel = getPriorityContext();
8453
addReplaceUpdate(fiber, state, callback || null, priorityLevel);
8554
scheduleUpdate(fiber, priorityLevel);
8655
},
8756
enqueueForceUpdate(instance, callback) {
88-
if (callback) {
89-
validateCallback(callback, 'forceUpdate');
90-
}
9157
const fiber = ReactInstanceMap.get(instance);
9258
const priorityLevel = getPriorityContext();
9359
addForceUpdate(fiber, callback || null, priorityLevel);

src/renderers/shared/fiber/ReactFiberUpdateQueue.js

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const {
2525
TaskPriority,
2626
} = require('ReactPriorityLevel');
2727

28+
const validateCallback = require('validateCallback');
2829
const warning = require('warning');
2930

3031
type PartialState<State, Props> =
@@ -204,12 +205,14 @@ function findInsertionPosition(queue, update) : Update | null {
204205
// we shouldn't make a copy.
205206
//
206207
// If the update is cloned, it returns the cloned update.
207-
function insertUpdate(fiber : Fiber, update : Update, methodName : ?string) : Update | null {
208+
function insertUpdate(fiber : Fiber, update : Update, methodName : string) : Update | null {
209+
validateCallback(update.callback, methodName);
210+
208211
const queue1 = ensureUpdateQueue(fiber);
209212
const queue2 = fiber.alternate ? ensureUpdateQueue(fiber.alternate) : null;
210213

211214
// Warn if an update is scheduled from inside an updater function.
212-
if (__DEV__ && typeof methodName === 'string') {
215+
if (__DEV__) {
213216
if (queue1.isProcessing || (queue2 && queue2.isProcessing)) {
214217
if (methodName === 'setState') {
215218
warning(
@@ -287,11 +290,7 @@ function addUpdate(
287290
isTopLevelUnmount: false,
288291
next: null,
289292
};
290-
if (__DEV__) {
291-
insertUpdate(fiber, update, 'setState');
292-
} else {
293-
insertUpdate(fiber, update);
294-
}
293+
insertUpdate(fiber, update, 'setState');
295294
}
296295
exports.addUpdate = addUpdate;
297296

@@ -310,12 +309,7 @@ function addReplaceUpdate(
310309
isTopLevelUnmount: false,
311310
next: null,
312311
};
313-
314-
if (__DEV__) {
315-
insertUpdate(fiber, update, 'replaceState');
316-
} else {
317-
insertUpdate(fiber, update);
318-
}
312+
insertUpdate(fiber, update, 'replaceState');
319313
}
320314
exports.addReplaceUpdate = addReplaceUpdate;
321315

@@ -333,11 +327,7 @@ function addForceUpdate(
333327
isTopLevelUnmount: false,
334328
next: null,
335329
};
336-
if (__DEV__) {
337-
insertUpdate(fiber, update, 'forceUpdate');
338-
} else {
339-
insertUpdate(fiber, update);
340-
}
330+
insertUpdate(fiber, update, 'forceUpdate');
341331
}
342332
exports.addForceUpdate = addForceUpdate;
343333

@@ -366,7 +356,7 @@ function addTopLevelUpdate(
366356
isTopLevelUnmount,
367357
next: null,
368358
};
369-
const update2 = insertUpdate(fiber, update);
359+
const update2 = insertUpdate(fiber, update, 'render');
370360

371361
if (isTopLevelUnmount) {
372362
// Drop all updates that are lower-priority, so that the tree is not

src/renderers/shared/stack/reconciler/ReactUpdateQueue.js

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,13 @@ var ReactInstanceMap = require('ReactInstanceMap');
1616
var ReactInstrumentation = require('ReactInstrumentation');
1717
var ReactUpdates = require('ReactUpdates');
1818

19-
var invariant = require('invariant');
2019
var warning = require('warning');
20+
var validateCallback = require('validateCallback');
2121

2222
function enqueueUpdate(internalInstance) {
2323
ReactUpdates.enqueueUpdate(internalInstance);
2424
}
2525

26-
function formatUnexpectedArgument(arg) {
27-
var type = typeof arg;
28-
if (type !== 'object') {
29-
return type;
30-
}
31-
var displayName = arg.constructor && arg.constructor.name || type;
32-
var keys = Object.keys(arg);
33-
if (keys.length > 0 && keys.length < 20) {
34-
return `${displayName} (keys: ${keys.join(', ')})`;
35-
}
36-
return displayName;
37-
}
38-
3926
function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
4027
var internalInstance = ReactInstanceMap.get(publicInstance);
4128
if (!internalInstance) {
@@ -253,15 +240,7 @@ var ReactUpdateQueue = {
253240
enqueueUpdate(internalInstance);
254241
},
255242

256-
validateCallback: function(callback, callerName) {
257-
invariant(
258-
!callback || typeof callback === 'function',
259-
'%s(...): Expected the last optional `callback` argument to be a ' +
260-
'function. Instead received: %s.',
261-
callerName,
262-
formatUnexpectedArgument(callback)
263-
);
264-
},
243+
validateCallback: validateCallback,
265244

266245
};
267246

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Copyright 2013-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule validateCallback
10+
* @flow
11+
*/
12+
13+
'use strict';
14+
15+
const invariant = require('invariant');
16+
17+
function formatUnexpectedArgument(arg: any) {
18+
let type = typeof arg;
19+
if (type !== 'object') {
20+
return type;
21+
}
22+
let displayName = arg.constructor && arg.constructor.name || type;
23+
let keys = Object.keys(arg);
24+
if (keys.length > 0 && keys.length < 20) {
25+
return `${displayName} (keys: ${keys.join(', ')})`;
26+
}
27+
return displayName;
28+
}
29+
30+
function validateCallback(callback: ?Function, callerName: string) {
31+
invariant(
32+
!callback || typeof callback === 'function',
33+
'%s(...): Expected the last optional `callback` argument to be a ' +
34+
'function. Instead received: %s.',
35+
callerName,
36+
formatUnexpectedArgument(callback)
37+
);
38+
}
39+
40+
module.exports = validateCallback;

0 commit comments

Comments
 (0)