Provide new jsx transform target for reactjs/rfcs#107 (#15141)
* adding jsx function * add more feature flag defaults * flip ReactElement order back
Ricky Vetter committed
Apr 7, 2019 at 15:02 UTC
745baf2e061bdb7a07b511fa6fd8c1c5e8106313
13 files changed
+659
-12
packages/react/src/React.js
+21
-1
@@ -22,6 +22,7 @@ import {
22
createFactory,
23
cloneElement,
24
isValidElement,
25
+ jsx,
26
} from './ReactElement';
27
import {createContext} from './ReactContext';
28
import {lazy} from './ReactLazy';
@@ -43,10 +44,16 @@ import {
44
createElementWithValidation,
45
createFactoryWithValidation,
46
cloneElementWithValidation,
47
+ jsxWithValidation,
48
+ jsxWithValidationStatic,
49
+ jsxWithValidationDynamic,
50
} from './ReactElementValidator';
51
import ReactSharedInternals from './ReactSharedInternals';
52
import {error, warn} from './withComponentStack';
49
-import {enableStableConcurrentModeAPIs} from 'shared/ReactFeatureFlags';
53
+import {
54
+ enableStableConcurrentModeAPIs,
55
+ enableJSXTransformAPI,
56
+} from 'shared/ReactFeatureFlags';
57
58
const React = {
59
Children: {
@@ -107,4 +114,17 @@ if (enableStableConcurrentModeAPIs) {
114
React.unstable_ConcurrentMode = undefined;
115
}
116
117
+if (enableJSXTransformAPI) {
118
+ if (__DEV__) {
119
+ React.jsxDEV = jsxWithValidation;
120
+ React.jsx = jsxWithValidationDynamic;
121
+ React.jsxs = jsxWithValidationStatic;
122
+ } else {
123
+ React.jsx = jsx;
124
+ // we may want to special case jsxs internally to take advantage of static children.
125
+ // for now we can ship identical prod functions
126
+ React.jsxs = jsx;
127
+ }
128
+}
129
+
130
export default React;
packages/react/src/ReactElement.js
+135
-2
@@ -95,8 +95,10 @@ function defineRefPropWarningGetter(props, displayName) {
95
* if something is a React Element.
96
*
97
* @param {*} type
98
+ * @param {*} props
99
* @param {*} key
100
* @param {string|object} ref
101
+ * @param {*} owner
102
* @param {*} self A *temporary* helper to detect places where `this` is
103
* different from the `owner` when React.createElement is called, so that we
104
* can warn. We want to get rid of owner and replace string `ref`s with arrow
@@ -104,8 +106,6 @@ function defineRefPropWarningGetter(props, displayName) {
106
* change in behavior.
107
* @param {*} source An annotation object (added by a transpiler or otherwise)
108
* indicating filename, line number, and/or other information.
107
- * @param {*} owner
108
- * @param {*} props
109
* @internal
110
*/
111
const ReactElement = function(type, key, ref, self, source, owner, props) {
@@ -164,6 +164,139 @@ const ReactElement = function(type, key, ref, self, source, owner, props) {
164
return element;
165
};
166
167
+/**
168
+ * https://github.com/reactjs/rfcs/pull/107
169
+ * @param {*} type
170
+ * @param {object} props
171
+ * @param {string} key
172
+ */
173
+export function jsx(type, config, maybeKey) {
174
+ let propName;
175
+
176
+ // Reserved names are extracted
177
+ const props = {};
178
+
179
+ let key = null;
180
+ let ref = null;
181
+
182
+ if (hasValidRef(config)) {
183
+ ref = config.ref;
184
+ }
185
+
186
+ if (hasValidKey(config)) {
187
+ key = '' + config.key;
188
+ }
189
+
190
+ // Remaining properties are added to a new props object
191
+ for (propName in config) {
192
+ if (
193
+ hasOwnProperty.call(config, propName) &&
194
+ !RESERVED_PROPS.hasOwnProperty(propName)
195
+ ) {
196
+ props[propName] = config[propName];
197
+ }
198
+ }
199
+
200
+ // intentionally not checking if key was set above
201
+ // this key is higher priority as it's static
202
+ if (maybeKey !== undefined) {
203
+ key = '' + maybeKey;
204
+ }
205
+
206
+ // Resolve default props
207
+ if (type && type.defaultProps) {
208
+ const defaultProps = type.defaultProps;
209
+ for (propName in defaultProps) {
210
+ if (props[propName] === undefined) {
211
+ props[propName] = defaultProps[propName];
212
+ }
213
+ }
214
+ }
215
+
216
+ return ReactElement(
217
+ type,
218
+ key,
219
+ ref,
220
+ undefined,
221
+ undefined,
222
+ ReactCurrentOwner.current,
223
+ props,
224
+ );
225
+}
226
+
227
+/**
228
+ * https://github.com/reactjs/rfcs/pull/107
229
+ * @param {*} type
230
+ * @param {object} props
231
+ * @param {string} key
232
+ */
233
+export function jsxDEV(type, config, maybeKey, source, self) {
234
+ let propName;
235
+
236
+ // Reserved names are extracted
237
+ const props = {};
238
+
239
+ let key = null;
240
+ let ref = null;
241
+
242
+ if (hasValidRef(config)) {
243
+ ref = config.ref;
244
+ }
245
+
246
+ if (hasValidKey(config)) {
247
+ key = '' + config.key;
248
+ }
249
+
250
+ // Remaining properties are added to a new props object
251
+ for (propName in config) {
252
+ if (
253
+ hasOwnProperty.call(config, propName) &&
254
+ !RESERVED_PROPS.hasOwnProperty(propName)
255
+ ) {
256
+ props[propName] = config[propName];
257
+ }
258
+ }
259
+
260
+ // intentionally not checking if key was set above
261
+ // this key is higher priority as it's static
262
+ if (maybeKey !== undefined) {
263
+ key = '' + maybeKey;
264
+ }
265
+
266
+ // Resolve default props
267
+ if (type && type.defaultProps) {
268
+ const defaultProps = type.defaultProps;
269
+ for (propName in defaultProps) {
270
+ if (props[propName] === undefined) {
271
+ props[propName] = defaultProps[propName];
272
+ }
273
+ }
274
+ }
275
+
276
+ if (key || ref) {
277
+ const displayName =
278
+ typeof type === 'function'
279
+ ? type.displayName || type.name || 'Unknown'
280
+ : type;
281
+ if (key) {
282
+ defineKeyPropWarningGetter(props, displayName);
283
+ }
284
+ if (ref) {
285
+ defineRefPropWarningGetter(props, displayName);
286
+ }
287
+ }
288
+
289
+ return ReactElement(
290
+ type,
291
+ key,
292
+ ref,
293
+ self,
294
+ source,
295
+ ReactCurrentOwner.current,
296
+ props,
297
+ );
298
+}
299
+
300
/**
301
* Create and return a new ReactElement of the given type.
302
* See https://reactjs.org/docs/react-api.html#createelement
packages/react/src/ReactElementValidator.js
+127
-9
@@ -27,7 +27,12 @@ import warning from 'shared/warning';
27
import warningWithoutStack from 'shared/warningWithoutStack';
28
29
import ReactCurrentOwner from './ReactCurrentOwner';
30
-import {isValidElement, createElement, cloneElement} from './ReactElement';
30
+import {
31
+ isValidElement,
32
+ createElement,
33
+ cloneElement,
34
+ jsxDEV,
35
+} from './ReactElement';
36
import ReactDebugCurrentFrame, {
37
setCurrentlyValidatingElement,
38
} from './ReactDebugCurrentFrame';
@@ -48,13 +53,8 @@ function getDeclarationErrorAddendum() {
53
return '';
54
}
55
51
-function getSourceInfoErrorAddendum(elementProps) {
52
- if (
53
- elementProps !== null &&
54
- elementProps !== undefined &&
55
- elementProps.__source !== undefined
56
- ) {
57
- const source = elementProps.__source;
56
+function getSourceInfoErrorAddendum(source) {
57
+ if (source !== undefined) {
58
const fileName = source.fileName.replace(/^.*[\\\/]/, '');
59
const lineNumber = source.lineNumber;
60
return '\n\nCheck your code at ' + fileName + ':' + lineNumber + '.';
@@ -62,6 +62,13 @@ function getSourceInfoErrorAddendum(elementProps) {
62
return '';
63
}
64
65
+function getSourceInfoErrorAddendumForProps(elementProps) {
66
+ if (elementProps !== null && elementProps !== undefined) {
67
+ return getSourceInfoErrorAddendum(elementProps.__source);
68
+ }
69
+ return '';
70
+}
71
+
72
/**
73
* Warn if there's no key explicitly set on dynamic arrays of children or
74
* object keys are not valid. This allows us to keep track of children between
@@ -259,6 +266,117 @@ function validateFragmentProps(fragment) {
266
setCurrentlyValidatingElement(null);
267
}
268
269
+export function jsxWithValidation(
270
+ type,
271
+ props,
272
+ key,
273
+ isStaticChildren,
274
+ source,
275
+ self,
276
+) {
277
+ const validType = isValidElementType(type);
278
+
279
+ // We warn in this case but don't throw. We expect the element creation to
280
+ // succeed and there will likely be errors in render.
281
+ if (!validType) {
282
+ let info = '';
283
+ if (
284
+ type === undefined ||
285
+ (typeof type === 'object' &&
286
+ type !== null &&
287
+ Object.keys(type).length === 0)
288
+ ) {
289
+ info +=
290
+ ' You likely forgot to export your component from the file ' +
291
+ "it's defined in, or you might have mixed up default and named imports.";
292
+ }
293
+
294
+ const sourceInfo = getSourceInfoErrorAddendum(source);
295
+ if (sourceInfo) {
296
+ info += sourceInfo;
297
+ } else {
298
+ info += getDeclarationErrorAddendum();
299
+ }
300
+
301
+ let typeString;
302
+ if (type === null) {
303
+ typeString = 'null';
304
+ } else if (Array.isArray(type)) {
305
+ typeString = 'array';
306
+ } else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) {
307
+ typeString = `<${getComponentName(type.type) || 'Unknown'} />`;
308
+ info =
309
+ ' Did you accidentally export a JSX literal instead of a component?';
310
+ } else {
311
+ typeString = typeof type;
312
+ }
313
+
314
+ warning(
315
+ false,
316
+ 'React.jsx: type is invalid -- expected a string (for ' +
317
+ 'built-in components) or a class/function (for composite ' +
318
+ 'components) but got: %s.%s',
319
+ typeString,
320
+ info,
321
+ );
322
+ }
323
+
324
+ const element = jsxDEV(type, props, key, source, self);
325
+
326
+ // The result can be nullish if a mock or a custom function is used.
327
+ // TODO: Drop this when these are no longer allowed as the type argument.
328
+ if (element == null) {
329
+ return element;
330
+ }
331
+
332
+ // Skip key warning if the type isn't valid since our key validation logic
333
+ // doesn't expect a non-string/function type and can throw confusing errors.
334
+ // We don't want exception behavior to differ between dev and prod.
335
+ // (Rendering will throw with a helpful message and as soon as the type is
336
+ // fixed, the key warnings will appear.)
337
+ if (validType) {
338
+ const children = props.children;
339
+ if (children !== undefined) {
340
+ if (isStaticChildren) {
341
+ for (let i = 0; i < children.length; i++) {
342
+ validateChildKeys(children[i], type);
343
+ }
344
+ } else {
345
+ validateChildKeys(children, type);
346
+ }
347
+ }
348
+ }
349
+
350
+ if (props.key !== undefined) {
351
+ warning(
352
+ false,
353
+ 'React.jsx: Spreading a key to JSX is a deprecated pattern. ' +
354
+ 'Explicitly pass a key after spreading props in your JSX call. ' +
355
+ 'E.g. <ComponentName {...props} key={key} />',
356
+ );
357
+ }
358
+
359
+ if (type === REACT_FRAGMENT_TYPE) {
360
+ validateFragmentProps(element);
361
+ } else {
362
+ validatePropTypes(element);
363
+ }
364
+
365
+ return element;
366
+}
367
+
368
+// These two functions exist to still get child warnings in dev
369
+// even with the prod transform. This means that jsxDEV is purely
370
+// opt-in behavior for better messages but that we won't stop
371
+// giving you warnings if you use production apis.
372
+export function jsxWithValidationStatic(type, props, key) {
373
+ return jsxWithValidation(type, props, key, true);
374
+}
375
+
376
+export function jsxWithValidationDynamic(type, props, key) {
377
+ return jsxWithValidation(type, props, key, false);
378
+}
379
+
380
export function createElementWithValidation(type, props, children) {
381
const validType = isValidElementType(type);
382
@@ -277,7 +395,7 @@ export function createElementWithValidation(type, props, children) {
395
"it's defined in, or you might have mixed up default and named imports.";
396
}
397
280
- const sourceInfo = getSourceInfoErrorAddendum(props);
398
+ const sourceInfo = getSourceInfoErrorAddendumForProps(props);
399
if (sourceInfo) {
400
info += sourceInfo;
401
} else {
packages/react/src/__tests__/ReactElementJSX-test.internal.js
new
+364
@@ -0,0 +1,364 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @emails react-core
8
+ */
9
+
10
+'use strict';
11
+
12
+let React;
13
+let ReactDOM;
14
+let ReactFeatureFlags;
15
+let ReactTestUtils;
16
+
17
+// NOTE: We're explicitly not using JSX here. This is intended to test
18
+// a new React.jsx api which does not have a JSX transformer yet.
19
+// A lot of these tests are pulled from ReactElement-test because
20
+// this api is meant to be backwards compatible.
21
+describe('ReactElement.jsx', () => {
22
+ let originalSymbol;
23
+
24
+ beforeEach(() => {
25
+ jest.resetModules();
26
+
27
+ // Delete the native Symbol if we have one to ensure we test the
28
+ // unpolyfilled environment.
29
+ originalSymbol = global.Symbol;
30
+ global.Symbol = undefined;
31
+
32
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
33
+ ReactFeatureFlags.enableJSXTransformAPI = true;
34
+
35
+ React = require('react');
36
+ ReactDOM = require('react-dom');
37
+ ReactTestUtils = require('react-dom/test-utils');
38
+ });
39
+
40
+ afterEach(() => {
41
+ global.Symbol = originalSymbol;
42
+ });
43
+
44
+ it('allows static methods to be called using the type property', () => {
45
+ class StaticMethodComponentClass extends React.Component {
46
+ render() {
47
+ return React.jsx('div', {});
48
+ }
49
+ }
50
+ StaticMethodComponentClass.someStaticMethod = () => 'someReturnValue';
51
+
52
+ const element = React.jsx(StaticMethodComponentClass, {});
53
+ expect(element.type.someStaticMethod()).toBe('someReturnValue');
54
+ });
55
+
56
+ it('identifies valid elements', () => {
57
+ class Component extends React.Component {
58
+ render() {
59
+ return React.jsx('div', {});
60
+ }
61
+ }
62
+
63
+ expect(React.isValidElement(React.jsx('div', {}))).toEqual(true);
64
+ expect(React.isValidElement(React.jsx(Component, {}))).toEqual(true);
65
+
66
+ expect(React.isValidElement(null)).toEqual(false);
67
+ expect(React.isValidElement(true)).toEqual(false);
68
+ expect(React.isValidElement({})).toEqual(false);
69
+ expect(React.isValidElement('string')).toEqual(false);
70
+ expect(React.isValidElement(React.createFactory('div'))).toEqual(false);
71
+ expect(React.isValidElement(Component)).toEqual(false);
72
+ expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
73
+
74
+ const jsonElement = JSON.stringify(React.jsx('div', {}));
75
+ expect(React.isValidElement(JSON.parse(jsonElement))).toBe(true);
76
+ });
77
+
78
+ it('is indistinguishable from a plain object', () => {
79
+ const element = React.jsx('div', {className: 'foo'});
80
+ const object = {};
81
+ expect(element.constructor).toBe(object.constructor);
82
+ });
83
+
84
+ it('should use default prop value when removing a prop', () => {
85
+ class Component extends React.Component {
86
+ render() {
87
+ return React.jsx('span', {});
88
+ }
89
+ }
90
+ Component.defaultProps = {fruit: 'persimmon'};
91
+
92
+ const container = document.createElement('div');
93
+ const instance = ReactDOM.render(
94
+ React.jsx(Component, {fruit: 'mango'}),
95
+ container,
96
+ );
97
+ expect(instance.props.fruit).toBe('mango');
98
+
99
+ ReactDOM.render(React.jsx(Component, {}), container);
100
+ expect(instance.props.fruit).toBe('persimmon');
101
+ });
102
+
103
+ it('should normalize props with default values', () => {
104
+ class Component extends React.Component {
105
+ render() {
106
+ return React.jsx('span', {children: this.props.prop});
107
+ }
108
+ }
109
+ Component.defaultProps = {prop: 'testKey'};
110
+
111
+ const instance = ReactTestUtils.renderIntoDocument(
112
+ React.jsx(Component, {}),
113
+ );
114
+ expect(instance.props.prop).toBe('testKey');
115
+
116
+ const inst2 = ReactTestUtils.renderIntoDocument(
117
+ React.jsx(Component, {prop: null}),
118
+ );
119
+ expect(inst2.props.prop).toBe(null);
120
+ });
121
+
122
+ it('throws when changing a prop (in dev) after element creation', () => {
123
+ class Outer extends React.Component {
124
+ render() {
125
+ const el = React.jsx('div', {className: 'moo'});
126
+
127
+ if (__DEV__) {
128
+ expect(function() {
129
+ el.props.className = 'quack';
130
+ }).toThrow();
131
+ expect(el.props.className).toBe('moo');
132
+ } else {
133
+ el.props.className = 'quack';
134
+ expect(el.props.className).toBe('quack');
135
+ }
136
+
137
+ return el;
138
+ }
139
+ }
140
+ const outer = ReactTestUtils.renderIntoDocument(
141
+ React.jsx(Outer, {color: 'orange'}),
142
+ );
143
+ if (__DEV__) {
144
+ expect(ReactDOM.findDOMNode(outer).className).toBe('moo');
145
+ } else {
146
+ expect(ReactDOM.findDOMNode(outer).className).toBe('quack');
147
+ }
148
+ });
149
+
150
+ it('throws when adding a prop (in dev) after element creation', () => {
151
+ const container = document.createElement('div');
152
+ class Outer extends React.Component {
153
+ render() {
154
+ const el = React.jsx('div', {children: this.props.sound});
155
+
156
+ if (__DEV__) {
157
+ expect(function() {
158
+ el.props.className = 'quack';
159
+ }).toThrow();
160
+ expect(el.props.className).toBe(undefined);
161
+ } else {
162
+ el.props.className = 'quack';
163
+ expect(el.props.className).toBe('quack');
164
+ }
165
+
166
+ return el;
167
+ }
168
+ }
169
+ Outer.defaultProps = {sound: 'meow'};
170
+ const outer = ReactDOM.render(React.jsx(Outer, {}), container);
171
+ expect(ReactDOM.findDOMNode(outer).textContent).toBe('meow');
172
+ if (__DEV__) {
173
+ expect(ReactDOM.findDOMNode(outer).className).toBe('');
174
+ } else {
175
+ expect(ReactDOM.findDOMNode(outer).className).toBe('quack');
176
+ }
177
+ });
178
+
179
+ it('does not warn for NaN props', () => {
180
+ class Test extends React.Component {
181
+ render() {
182
+ return React.jsx('div', {});
183
+ }
184
+ }
185
+ const test = ReactTestUtils.renderIntoDocument(
186
+ React.jsx(Test, {value: +undefined}),
187
+ );
188
+ expect(test.props.value).toBeNaN();
189
+ });
190
+
191
+ it('should warn when `key` is being accessed on composite element', () => {
192
+ const container = document.createElement('div');
193
+ class Child extends React.Component {
194
+ render() {
195
+ return React.jsx('div', {children: this.props.key});
196
+ }
197
+ }
198
+ class Parent extends React.Component {
199
+ render() {
200
+ return React.jsxs('div', {
201
+ children: [
202
+ React.jsx(Child, {}, '0'),
203
+ React.jsx(Child, {}, '1'),
204
+ React.jsx(Child, {}, '2'),
205
+ ],
206
+ });
207
+ }
208
+ }
209
+ expect(() => ReactDOM.render(React.jsx(Parent, {}), container)).toWarnDev(
210
+ 'Child: `key` is not a prop. Trying to access it will result ' +
211
+ 'in `undefined` being returned. If you need to access the same ' +
212
+ 'value within the child component, you should pass it as a different ' +
213
+ 'prop. (https://fb.me/react-special-props)',
214
+ {withoutStack: true},
215
+ );
216
+ });
217
+
218
+ it('should warn when `key` is being accessed on a host element', () => {
219
+ const element = React.jsxs('div', {}, '3');
220
+ expect(() => void element.props.key).toWarnDev(
221
+ 'div: `key` is not a prop. Trying to access it will result ' +
222
+ 'in `undefined` being returned. If you need to access the same ' +
223
+ 'value within the child component, you should pass it as a different ' +
224
+ 'prop. (https://fb.me/react-special-props)',
225
+ {withoutStack: true},
226
+ );
227
+ });
228
+
229
+ it('should warn when `ref` is being accessed', () => {
230
+ const container = document.createElement('div');
231
+ class Child extends React.Component {
232
+ render() {
233
+ return React.jsx('div', {children: this.props.ref});
234
+ }
235
+ }
236
+ class Parent extends React.Component {
237
+ render() {
238
+ return React.jsx('div', {
239
+ children: React.jsx(Child, {ref: 'childElement'}),
240
+ });
241
+ }
242
+ }
243
+ expect(() => ReactDOM.render(React.jsx(Parent, {}), container)).toWarnDev(
244
+ 'Child: `ref` is not a prop. Trying to access it will result ' +
245
+ 'in `undefined` being returned. If you need to access the same ' +
246
+ 'value within the child component, you should pass it as a different ' +
247
+ 'prop. (https://fb.me/react-special-props)',
248
+ {withoutStack: true},
249
+ );
250
+ });
251
+
252
+ it('identifies elements, but not JSON, if Symbols are supported', () => {
253
+ // Rudimentary polyfill
254
+ // Once all jest engines support Symbols natively we can swap this to test
255
+ // WITH native Symbols by default.
256
+ const REACT_ELEMENT_TYPE = function() {}; // fake Symbol
257
+ const OTHER_SYMBOL = function() {}; // another fake Symbol
258
+ global.Symbol = function(name) {
259
+ return OTHER_SYMBOL;
260
+ };
261
+ global.Symbol.for = function(key) {
262
+ if (key === 'react.element') {
263
+ return REACT_ELEMENT_TYPE;
264
+ }
265
+ return OTHER_SYMBOL;
266
+ };
267
+
268
+ jest.resetModules();
269
+
270
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
271
+ ReactFeatureFlags.enableJSXTransformAPI = true;
272
+
273
+ React = require('react');
274
+
275
+ class Component extends React.Component {
276
+ render() {
277
+ return React.jsx('div');
278
+ }
279
+ }
280
+
281
+ expect(React.isValidElement(React.jsx('div', {}))).toEqual(true);
282
+ expect(React.isValidElement(React.jsx(Component, {}))).toEqual(true);
283
+
284
+ expect(React.isValidElement(null)).toEqual(false);
285
+ expect(React.isValidElement(true)).toEqual(false);
286
+ expect(React.isValidElement({})).toEqual(false);
287
+ expect(React.isValidElement('string')).toEqual(false);
288
+ expect(React.isValidElement(React.createFactory('div'))).toEqual(false);
289
+ expect(React.isValidElement(Component)).toEqual(false);
290
+ expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
291
+
292
+ const jsonElement = JSON.stringify(React.jsx('div', {}));
293
+ expect(React.isValidElement(JSON.parse(jsonElement))).toBe(false);
294
+ });
295
+
296
+ it('should warn when unkeyed children are passed to jsx', () => {
297
+ const container = document.createElement('div');
298
+ class Child extends React.Component {
299
+ render() {
300
+ return React.jsx('div', {});
301
+ }
302
+ }
303
+ class Parent extends React.Component {
304
+ render() {
305
+ return React.jsx('div', {
306
+ children: [
307
+ React.jsx(Child, {}),
308
+ React.jsx(Child, {}),
309
+ React.jsx(Child, {}),
310
+ ],
311
+ });
312
+ }
313
+ }
314
+ expect(() => ReactDOM.render(React.jsx(Parent, {}), container)).toWarnDev(
315
+ 'Warning: Each child in a list should have a unique "key" prop.\n\n' +
316
+ 'Check the render method of `Parent`. See https://fb.me/react-warning-keys for more information.\n' +
317
+ ' in Child (created by Parent)\n' +
318
+ ' in Parent',
319
+ );
320
+ });
321
+
322
+ it('should warn when keys are passed as part of props', () => {
323
+ const container = document.createElement('div');
324
+ class Child extends React.Component {
325
+ render() {
326
+ return React.jsx('div', {});
327
+ }
328
+ }
329
+ class Parent extends React.Component {
330
+ render() {
331
+ return React.jsx('div', {
332
+ children: [React.jsx(Child, {key: '0'})],
333
+ });
334
+ }
335
+ }
336
+ expect(() => ReactDOM.render(React.jsx(Parent, {}), container)).toWarnDev(
337
+ 'Warning: React.jsx: Spreading a key to JSX is a deprecated pattern. ' +
338
+ 'Explicitly pass a key after spreading props in your JSX call. ' +
339
+ 'E.g. <ComponentName {...props} key={key} />',
340
+ );
341
+ });
342
+
343
+ it('should not warn when unkeyed children are passed to jsxs', () => {
344
+ const container = document.createElement('div');
345
+ class Child extends React.Component {
346
+ render() {
347
+ return React.jsx('div', {});
348
+ }
349
+ }
350
+ class Parent extends React.Component {
351
+ render() {
352
+ return React.jsxs('div', {
353
+ children: [
354
+ React.jsx(Child, {}),
355
+ React.jsx(Child, {}),
356
+ React.jsx(Child, {}),
357
+ ],
358
+ });
359
+ }
360
+ }
361
+ // TODO: an explicit expect for no warning?
362
+ ReactDOM.render(React.jsx(Parent, {}), container);
363
+ });
364
+});
packages/shared/ReactFeatureFlags.js
+3
@@ -68,3 +68,6 @@ export const enableEventAPI = false;
68
// Enables rewritten version of ReactFiberScheduler. Added in case we need to
69
// quickly revert it.
70
export const enableNewScheduler = false;
71
+
72
+// New API for JSX transforms to target - https://github.com/reactjs/rfcs/pull/107
73
+export const enableJSXTransformAPI = false;
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -32,6 +32,7 @@ export const warnAboutDeprecatedLifecycles = true;
32
export const warnAboutDeprecatedSetNativeProps = true;
33
export const enableEventAPI = false;
34
export const enableNewScheduler = false;
35
+export const enableJSXTransformAPI = false;
36
37
// Only used in www builds.
38
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -29,6 +29,7 @@ export const enableSchedulerDebugging = false;
29
export const warnAboutDeprecatedSetNativeProps = false;
30
export const enableEventAPI = false;
31
export const enableNewScheduler = false;
32
+export const enableJSXTransformAPI = false;
33
34
// Only used in www builds.
35
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.new-scheduler.js
+1
@@ -26,5 +26,6 @@ export const enableStableConcurrentModeAPIs = false;
26
export const warnAboutShorthandPropertyCollision = false;
27
export const warnAboutDeprecatedSetNativeProps = false;
28
export const enableEventAPI = false;
29
+export const enableJSXTransformAPI = false;
30
31
export const enableNewScheduler = true;
packages/shared/forks/ReactFeatureFlags.persistent.js
+1
@@ -29,6 +29,7 @@ export const enableSchedulerDebugging = false;
29
export const warnAboutDeprecatedSetNativeProps = false;
30
export const enableEventAPI = false;
31
export const enableNewScheduler = false;
32
+export const enableJSXTransformAPI = false;
33
34
// Only used in www builds.
35
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -29,6 +29,7 @@ export const enableSchedulerDebugging = false;
29
export const warnAboutDeprecatedSetNativeProps = false;
30
export const enableEventAPI = false;
31
export const enableNewScheduler = false;
32
+export const enableJSXTransformAPI = false;
33
34
// Only used in www builds.
35
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -27,6 +27,7 @@ export const disableJavaScriptURLs = false;
27
export const disableYielding = false;
28
export const enableEventAPI = true;
29
export const enableNewScheduler = false;
30
+export const enableJSXTransformAPI = true;
31
32
// Only used in www builds.
33
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.www-new-scheduler.js
+1
@@ -31,6 +31,7 @@ export {
31
} from './ReactFeatureFlags.www';
32
33
export const enableNewScheduler = true;
34
+export const enableJSXTransformAPI = true;
35
36
// Flow magic to verify the exports of this file match the original version.
37
// eslint-disable-next-line no-unused-vars
packages/shared/forks/ReactFeatureFlags.www.js
+2
@@ -74,6 +74,8 @@ function updateFlagOutsideOfReactCallStack() {
74
75
export const enableEventAPI = true;
76
77
+export const enableJSXTransformAPI = true;
78
+
79
// Flow magic to verify the exports of this file match the original version.
80
// eslint-disable-next-line no-unused-vars
81
type Check<_X, Y: _X, X: Y = _X> = null;