facebook / jsx

The JSX specification is a XML-like syntax extension to ECMAScript.

Home Page:http://facebook.github.io/jsx/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support JSX attributes with ES2015 computed property names

dantman opened this issue · comments

I'd like to propose we support JSX attributes with ES2015's computed property names

I am opening a separate issue from "AssignmentExpression in JSXAttributeName#21" because:

  • The opening comment of that issue proposes custom {attr}="value" syntax using curly braces; I believe we should stick with the same ComputedPropertyName used by ES2015 and not invent our own. Though this is up to discussion if the maintainers of the spec think that custom syntax fits in better with JSX.
  • Over half the comments in that issue are now about things unrelated to the proposal, so it's not a productive place to discuss since people new to the discussion have to wade through pages of unrelated comments first.
  • I wanted to include a spec, use cases, and information up front instead of deep in the comments.

Spec

The following update to JSXAttribute in the draft spec should be sufficient to my knowledge. New portion underlined.

JSXAttribute :


  • JSXAttributeName JSXAttributeInitializeropt
  • ComputedPropertyName JSXAttributeInitializer

This is not part of the spec, but for reference here is the ComputedPropertyName definition from ECMA-262.

ComputedPropertyName[Yield] :

  • [ AssignmentExpression[In, ?Yield] ]

Details

Here are some example JSX -> JS mappings. I've mixed existing syntax mappings with new ones to show how the new syntax relates to the old syntax.

<Foo bar='baz' />; // Existing
React.createElement(Foo, { bar: 'baz' });

<Foo bar />; // Existing
React.createElement(Foo, { bar: true });

<Foo {...{bar: 'baz'}} />; // Existing
React.createElement(Foo, { bar: 'baz' });

<Foo [bar]='baz' />; // New
React.createElement(Foo, { [bar]: 'baz' });

<Foo {...{[bar]: 'baz'}} />; // Existing
React.createElement(Foo, { [bar]: 'baz' });

The following syntax is not valid:

<Foo [bar] />;
<Foo [ns]:name='foo' />;
<Foo ns:[name]='foo' />;
<Foo [ns]:[name]='foo' />;
<Foo [ns:name]='foo' />; // This is not invalid from the JSX portion of the spec,
                         // it's invalid because `ns:name` is an invalid AssignmentExpression.

Like we have a special key-only form of JSXAttribute, ES6 has a special {name}-only form of PropertyDefinition. But {[expr]} is not a valid ObjectLiteral in ES6.
Given that fact and it's unclear what [bar] would mean to JSX we should not include a mapping for the JSXAttributeInitializer-less form of JSXAttribute with a computed property.

It also doesn't make sense to allow mixing of computed properties and namespaced attribute names. Especially since computed property syntax with strings already allows you to include the : within the computed property.

These are taken care of in the spec by defining the syntax as another form of JSXAttribute without the opt and without using JSXAttributeName. Like how ES-262 has separate PropertyDefinition forms for PropertyName: AssignmentExpression and IdentifierReference.

Motivation

Computed property names are part of the ES2015 syntax for object literals. They are no less useful in JSX. Some ideas of what you could do with computed properties in JSX.

Vary the prop name on some condition.

// type examples: 'text' and 'checkbox'
<input
  type={type}
  name={fieldName}
  [type === 'checkbox' ? 'checked' : 'value']={formValues[fieldName]} />

// https://github.com/facebook/react-native/issues/7135
const testId = Platform.OS === 'android' && process.env.NOD_ENV === 'test'
  ? 'accessibleLabel'
  : 'testId';
<View [testId]='my_id' />

Pass symbols as prop names, allowing you to make internal/private props for a component or have a prop passed through 3rd party components without worrying about name collisions.

// https://facebook.github.io/react-native/docs/direct-manipulation.html#setnativeprops-to-clear-textinput-value
// But with a field component in between hiding the TextInput,
// but exposing it with props[nativeRef] for the few cases you need a workaround
const nativeRef = Symbol('nativeRef');
const MyInput = (props) => (
  <View style={fieldStyles}>
    <Label text={props.label}
    <TextInput ref={props[nativeRef]} value={props.value} />
  </View>
);

const inputRef = React.createRef();
<MyInput [nativeRef]={inputRef} />
inputRef.value..setNativeProps({text: ''});

An alternative to attrs: {} and events: {} allowing libraries like skatejs/val to differentiate between attributes and props. By using a function that returns a Symbol the library has registered a Symbol -> attribute mapping for. Or just returning it as a string prop name with some long prefix like const attr = (name) => '__SKATEJS_VAL_ATTRIBUTE__$' + name;.

// h.js
import { createElement } from 'react';
import val, {attr, event} from '@skatejs/val';

export default val(createElement);
export {attr, event};

// app.js
/* @jsx h */
import h, {attr, event} from 'h';

<WebComponent
  someProperty='bar'
  [attr('some-attribute')]='foo'
  [event('some-event')]={handleEvent} />

More possibilities if the "Registered prop as ref / [refProp]={value} and [customEvent]={handler}" RFC is accepted.

IMO <Foo [bar] /> should function the same as <Foo [bar]={true} /> — this isn’t destructuring, and there really isn’t a reason as far as I can see to disallow this.

IMO <Foo [bar] /> should function the same as <Foo [bar]={true} /> — this isn’t destructuring, and there really isn’t a reason as far as I can see to disallow this.

It has no analog, [bar]= is pretty clearly the JSX version of [bar]: in ES2015. But [bar] looks more like array syntax randomly placed in an element.

However if the spec owners decide, or the consensus becomes, that we should allow this syntax I can update the proposal.