Skip to content

Instantly share code, notes, and snippets.

@zpao
Last active December 21, 2015 14:48
Show Gist options
  • Save zpao/6321691 to your computer and use it in GitHub Desktop.
Save zpao/6321691 to your computer and use it in GitHub Desktop.
JSX DOM Conventions proposals

Conventions for React DOM & JSX

There's been a lot of discussion lately around how React should use JSX to describe DOM nodes. Thanks to @piranha for sparking this discussion in facebook/react#269

To summarize the current convention of React DOM nodes such as <div /> and <span />.

  • All React DOM elements expect camelCased versions of attributes.
  • camelCasing is natural in a JavaScript-centric programming paradigm (like React).
  • the camelCasing of DOM matches what you would write for custom components:
    • <Typeahead onClick=../> and <div onClick=.. />
  • These camelCased attributes are usually what you write when updating the DOM via JS (input.maxLength).
  • One current confusing exception is that class= is normalized automatically into className= at transform time.

One thing we've mostly agreed on is that we should be consistent. If you write <Typeahead class="hi" />, then Typeahead, should access it as class. Anything else would be awkward and inconsistent. The React core should let you, as the inventor of custom React components, decide the style conventions of your public API.

Reactive JS DOM simultaneously expresses both creation and updating of the DOM, so the JavaScript-centric convention is as reasonable as any. However, the current convention has caused some confusion to new React developers. So it's time now for us to make a decision about the future of how we write JSX and handle DOM attributes in React. We'd like to arrive at a simple consistent convention that also prevents new-developer friction/surprises.

Option 1: Do (Almost) Nothing

  • Stop automatically transforming class=, only support className=
  • No changes to React, only in the transformer
  • In order to have a DOM prop, it must be camelCase
  • Warn in __DEV__ when we see a key on a ReactNativeComponent that we don't understand. We can do case-insensitive comparisons and suggest the right thing (e.g., see tabindex, suggest tabIndex) - see facebook/react#267
<MyComponent tabindex={1} />
MyComponent({tabindex: 1});
this.props.tabindex; // 1
this.props.tabIndex; // undefined
this.transferPropsTo(<div />); // will warn about tabindex

Option2: Fix with Transform:

  • Write whatever case you want (just like HTML)
  • JSX transformer will look for HTML specific keys and convert them to camelCased.
  • No change in React Core code, onus is on transformer.
  • Transformation would also occur on custom components
    • <Typeahead autofocus /> becomes <Typeahead autoFocus />
    • Typeahead reads property as this.props.autoFocus regardless of how it was supplied.
    • Case convertion only happens for DOM looking attributes.
  • Stop supporting class=, only support className=
<MyComponent TABINDEX={1} />
MyComponent({tabIndex: 1});
this.props.TABINDEX; // undefined
this.props.tabIndex; // 1
this.transferPropsTo(<div />); // works

Option 3: WYSIWYG and Forgiving DOM

As a React developer of custom components, what you write, is always what you read. The transformer never automatically converts attributes.

// Typeahead would read the property as this.props.tabIndex
<Typeahead tabIndex={2} /> 

// Teacher reads its props as this.props.className
<Teacher className="Biology101" />

// ClassyNightClub reads its as this.props.class or
// this.props['class'] if you care about IE.
<ClassyNightClub class="supper_classy" />

// Marxist reads its props as this.props.class or
// this.props['class'] if you care about IE.
<Marxist class="Proletariat" />


// The doc reads its prop as this.props.readOnly (even though readOnly is
// a DOM name - nothing automatically transforms it).
<Document readOnly={true} />

// The Typeahead reads its prop as onClick.
<Typeahead onClick={...} />

// CrazyShape reads its props exactly as it was supplied.
// this.props['stroke-width'] - the only way to access it in js.
<CrazyShape stroke-width="1px" />

The native DOM will be extremely liberal with what it accepts. Developers never invent DOM components, so all this hacky resolution is done in the React core - developers are never faced with it.

// Works
<div className="1" />

// Works
<div classname="1" />

// Works
<div class="1" />

// Works
<div onclick={...} />

// Works
<div onClick={...} />

// WORKS!
var Typeahead = React.createClass({
  // Typeahead's public API is that it accepts an `onClick` - not an
  // `onclick`. Div is totally fine with this if you transfer props to. The
  // click is still wired up.
  propTypes: {
    onClick: 'function',   // One camel cased
    autofocus: 'boolean'   // One not!
  },
  render: function() {
    return this.transferPropsTo(<div />);
  }
});

Optionally, we can accept variations of camelCase instead of hyphens if we like. (Don't need to decide now).

// Works.
<div stroke-width="1" />
<div strokeWidth="1" />

The summary of proposal #3 is:

  • You always read exactly what you wrote.
  • The DOM happens to be very forgiving and work with whatever your organization's conventions are. Opinionated people can write code the way they want and lint against all alternative forms.
  • For consistency, you and your group should really chose one convention and pretend as if the other forms are invalid, even if the React DOM forgives you.
  • Nice side-effect of #3: if you copy/paste html from a web page, it will usually just work.
  • But really, you should be using camelCase for everything because then your DOM API matches the style of custom components.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment