react: `inlineElements` optimization breaks on older browsers; discussion about Symbols and React.elementFromObject() API

See this https://github.com/babel/babel/issues/2517 and the associated discussion around the PR https://github.com/babel/babel/pull/2518, which I don’t expect to be merged due to loader issues.

To recap:

The inlineElements optimization requires brittle knowledge of internal React values, namely, $$typeof. This breaks on older browsers unless the developer globally polyfills Symbol, because Symbol will be polyfilled automatically by Babel in the user’s code, but will not be polyfilled in the React library. This causes ReactElement.isValidElement to fail as Symbol.for('react.element') !== 0xeac7.

Worse, this bug only occurs in older browsers that don’t implement Symbol, meaning that many devs won’t catch it right away as it will work fine in FF, Chrome, and (latest) Safari.

This is a hard issue to fix without globally polyfilling Symbol or giving up on the use of Symbol for $$typeof. Babel could automatically this as part of enabling the optimisation, but @loganfsmyth had a better idea - how about a React.elementFromObject() API?

This function would be nothing more than:

React.elementFromObject = function(obj) {
  invariant(obj && typeof obj === 'object', "Supply an object to React.elementFromObject.");
  obj.$$typeof = REACT_ELEMENT_TYPE;
  return obj;
}

This ensures that the REACT_ELEMENT_TYPE we are using is equal to the one used in ReactElement.isValidElement. It shouldn’t be necessary to do any validation in elementFromObject because it will be caught by isValidElement later on.

Thoughts?

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Comments: 30 (15 by maintainers)

Commits related to this issue

Most upvoted comments

@shubhamsizzles For now I put this in my browser entrypoint so all libs are using the same Symbol.

global.Symbol = require('core-js/es6/symbol'); // Fix react.inlineElements

Any progress of this? Is there a workaround for this till the issue is fixed that doesn’t involve adding babel-polyfill to the entry point? I tried using es6-symbol/implement polyfill, but that didn’t work.

This problem is still present and will cause unexpected breakage in older browsers in production builds only.

Since profiling has shown that the function call is actually not the bottleneck, I think it makes sense to put this back on the table as a blessed API (if even somewhat undocumented) as not to break apps in production. It would be great for interop with other optimizing runtimes as a fast path alternative to React.createElement() with fewer validations.

Alternatively, could the ReactElement constructor (not ReactElement.createElement) simply be exposed?

Babel helpers have this interesting desire to remain “pure”, but I don’t consider relying on a Symbol polyfill (and failing interop between a polyfill and native) to be pure. This seems to be the only long-term solution that will fix this for good.

The purpose of inlining the elements is to get rid of function calls so I don’t think adding a React.elementFromObject call is a good idea since it defeats the point.