Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion packages/react/src/React.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
createFactory,
cloneElement,
isValidElement,
jsx,
} from './ReactElement';
import {createContext} from './ReactContext';
import {lazy} from './ReactLazy';
Expand All @@ -43,10 +44,16 @@ import {
createElementWithValidation,
createFactoryWithValidation,
cloneElementWithValidation,
jsxWithValidation,
jsxWithValidationStatic,
jsxWithValidationDynamic,
} from './ReactElementValidator';
import ReactSharedInternals from './ReactSharedInternals';
import {error, warn} from './withComponentStack';
import {enableStableConcurrentModeAPIs} from 'shared/ReactFeatureFlags';
import {
enableStableConcurrentModeAPIs,
enableJSXTransformAPI,
} from 'shared/ReactFeatureFlags';

const React = {
Children: {
Expand Down Expand Up @@ -107,4 +114,17 @@ if (enableStableConcurrentModeAPIs) {
React.unstable_ConcurrentMode = undefined;
}

if (enableJSXTransformAPI) {
if (__DEV__) {
React.jsxDEV = jsxWithValidation;
React.jsx = jsxWithValidationDynamic;
React.jsxs = jsxWithValidationStatic;
} else {
React.jsx = jsx;
// we may want to special case jsxs internally to take advantage of static children.
// for now we can ship identical prod functions
React.jsxs = jsx;
}
}

export default React;
137 changes: 135 additions & 2 deletions packages/react/src/ReactElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,17 @@ function defineRefPropWarningGetter(props, displayName) {
* if something is a React Element.
*
* @param {*} type
* @param {*} props
* @param {*} key
* @param {string|object} ref
* @param {*} owner
* @param {*} self A *temporary* helper to detect places where `this` is
* different from the `owner` when React.createElement is called, so that we
* can warn. We want to get rid of owner and replace string `ref`s with arrow
* functions, and as long as `this` and owner are the same, there will be no
* change in behavior.
* @param {*} source An annotation object (added by a transpiler or otherwise)
* indicating filename, line number, and/or other information.
* @param {*} owner
* @param {*} props
* @internal
*/
const ReactElement = function(type, key, ref, self, source, owner, props) {
Expand Down Expand Up @@ -164,6 +164,139 @@ const ReactElement = function(type, key, ref, self, source, owner, props) {
return element;
};

/**
* https://github.com/reactjs/rfcs/pull/107
* @param {*} type
* @param {object} props
* @param {string} key
*/
export function jsx(type, config, maybeKey) {
let propName;

// Reserved names are extracted
const props = {};

let key = null;
let ref = null;

if (hasValidRef(config)) {
ref = config.ref;
}

if (hasValidKey(config)) {
key = '' + config.key;
}

// Remaining properties are added to a new props object
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}

// intentionally not checking if key was set above
// this key is higher priority as it's static
if (maybeKey !== undefined) {
key = '' + maybeKey;
}

// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}

return ReactElement(
type,
key,
ref,
undefined,
undefined,
ReactCurrentOwner.current,
props,
);
}

/**
* https://github.com/reactjs/rfcs/pull/107
* @param {*} type
* @param {object} props
* @param {string} key
*/
export function jsxDEV(type, config, maybeKey, source, self) {
let propName;

// Reserved names are extracted
const props = {};

let key = null;
let ref = null;

if (hasValidRef(config)) {
ref = config.ref;
}

if (hasValidKey(config)) {
key = '' + config.key;
}

// Remaining properties are added to a new props object
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}

// intentionally not checking if key was set above
// this key is higher priority as it's static
if (maybeKey !== undefined) {
key = '' + maybeKey;
}

// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}

if (key || ref) {
const displayName =
typeof type === 'function'
? type.displayName || type.name || 'Unknown'
: type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}

return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}

/**
* Create and return a new ReactElement of the given type.
* See https://reactjs.org/docs/react-api.html#createelement
Expand Down
136 changes: 127 additions & 9 deletions packages/react/src/ReactElementValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ import warning from 'shared/warning';
import warningWithoutStack from 'shared/warningWithoutStack';

import ReactCurrentOwner from './ReactCurrentOwner';
import {isValidElement, createElement, cloneElement} from './ReactElement';
import {
isValidElement,
createElement,
cloneElement,
jsxDEV,
} from './ReactElement';
import ReactDebugCurrentFrame, {
setCurrentlyValidatingElement,
} from './ReactDebugCurrentFrame';
Expand All @@ -48,20 +53,22 @@ function getDeclarationErrorAddendum() {
return '';
}

function getSourceInfoErrorAddendum(elementProps) {
if (
elementProps !== null &&
elementProps !== undefined &&
elementProps.__source !== undefined
) {
const source = elementProps.__source;
function getSourceInfoErrorAddendum(source) {
if (source !== undefined) {
const fileName = source.fileName.replace(/^.*[\\\/]/, '');
const lineNumber = source.lineNumber;
return '\n\nCheck your code at ' + fileName + ':' + lineNumber + '.';
}
return '';
}

function getSourceInfoErrorAddendumForProps(elementProps) {
if (elementProps !== null && elementProps !== undefined) {
return getSourceInfoErrorAddendum(elementProps.__source);
}
return '';
}

/**
* Warn if there's no key explicitly set on dynamic arrays of children or
* object keys are not valid. This allows us to keep track of children between
Expand Down Expand Up @@ -259,6 +266,117 @@ function validateFragmentProps(fragment) {
setCurrentlyValidatingElement(null);
}

export function jsxWithValidation(
type,
props,
key,
isStaticChildren,
source,
self,
) {
const validType = isValidElementType(type);

// We warn in this case but don't throw. We expect the element creation to
// succeed and there will likely be errors in render.
if (!validType) {
let info = '';
if (
type === undefined ||
(typeof type === 'object' &&
type !== null &&
Object.keys(type).length === 0)
) {
info +=
' You likely forgot to export your component from the file ' +
"it's defined in, or you might have mixed up default and named imports.";
}

const sourceInfo = getSourceInfoErrorAddendum(source);
if (sourceInfo) {
info += sourceInfo;
} else {
info += getDeclarationErrorAddendum();
}

let typeString;
if (type === null) {
typeString = 'null';
} else if (Array.isArray(type)) {
typeString = 'array';
} else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) {
typeString = `<${getComponentName(type.type) || 'Unknown'} />`;
info =
' Did you accidentally export a JSX literal instead of a component?';
} else {
typeString = typeof type;
}

warning(
false,
'React.jsx: type is invalid -- expected a string (for ' +
'built-in components) or a class/function (for composite ' +
'components) but got: %s.%s',
typeString,
info,
);
}

const element = jsxDEV(type, props, key, source, self);

// The result can be nullish if a mock or a custom function is used.
// TODO: Drop this when these are no longer allowed as the type argument.
if (element == null) {
return element;
}

// Skip key warning if the type isn't valid since our key validation logic
// doesn't expect a non-string/function type and can throw confusing errors.
// We don't want exception behavior to differ between dev and prod.
// (Rendering will throw with a helpful message and as soon as the type is
// fixed, the key warnings will appear.)
if (validType) {
const children = props.children;
if (children !== undefined) {
if (isStaticChildren) {
for (let i = 0; i < children.length; i++) {
validateChildKeys(children[i], type);
}
} else {
validateChildKeys(children, type);
}
}
}

if (props.key !== undefined) {
warning(
false,
'React.jsx: Spreading a key to JSX is a deprecated pattern. ' +
'Explicitly pass a key after spreading props in your JSX call. ' +
'E.g. <ComponentName {...props} key={key} />',
);
}

if (type === REACT_FRAGMENT_TYPE) {
validateFragmentProps(element);
} else {
validatePropTypes(element);
}

return element;
}

// These two functions exist to still get child warnings in dev
// even with the prod transform. This means that jsxDEV is purely
// opt-in behavior for better messages but that we won't stop
// giving you warnings if you use production apis.
export function jsxWithValidationStatic(type, props, key) {
return jsxWithValidation(type, props, key, true);
}

export function jsxWithValidationDynamic(type, props, key) {
return jsxWithValidation(type, props, key, false);
}

export function createElementWithValidation(type, props, children) {
const validType = isValidElementType(type);

Expand All @@ -277,7 +395,7 @@ export function createElementWithValidation(type, props, children) {
"it's defined in, or you might have mixed up default and named imports.";
}

const sourceInfo = getSourceInfoErrorAddendum(props);
const sourceInfo = getSourceInfoErrorAddendumForProps(props);
if (sourceInfo) {
info += sourceInfo;
} else {
Expand Down
Loading