ponyfoo.com

React, JSX and ES6: The Weird Parts

Fix
A relevant ad will be displayed here soon. These ads help pay for my hosting.
Please consider disabling your ad blocker on Pony Foo. These ads help pay for my hosting.
You can support Pony Foo directly through Patreon or via PayPal.

I’ve spent a few days working with JSX and React and I have mixed feelings about them. React is pretty neat, but I find that they made some very unusual choices when it comes to their API design. Then there’s JSX, definitely the weirdest aspect of React – we’ll look into it as well. I’ve really enjoyed the ES6 and Babel experience, although I’ve noticed that there’s a learning curve where you start to decide whether using an ES6 feature is better than its ES5 equivalent or not, something we’ll explore towards the end of the article.

1961 Buick "Flamingo" with rotating front seat
1961 Buick "Flamingo" with rotating front seat

JSX is the new XHTML

Web-oriented template engines typically consist of an entirely new language Jade, Dust.js, etc – or a few extensions on top of HTML Mustache, Hogan, Nunjucks, etc. We rarely see hybrids like JSX, a language that’s more or less aligned with XHTML but which also comes with some gross limitations in what you’re able to do with it, alongside “cool” features like being able to mix it with JavaScript code. In that last aspect, it’s sort of like Jade, which you can also mix with JavaScript, but I’ll admit that mixing JavaScript and JSX is way cleaner than mixing JavaScript with Jade.

React being one of the communities that push ES6 most aggressively, I would’ve liked to see them implement views largely based around ES6 string interpolation. Of course, JSX was created so that you don’t have to use React’s virtual DOM API, which can get very verbose with a method call for each DOM element. But still, it would’ve been nice if they took a more ES6ish approach. And boy is JSX weird. In fact, the point about DOM elements brings me to an unfortunate side effect of JSX.

Unexpected and automatic DOM element insertion

As a newcomer to React, the fact that this:

<span>foo {'bar'} {'baz'}</span>

Is turned into that:

<span data-reactid=".1gm29bnrabk.1.0">
  <span data-reactid=".1gm29bnrabk.1.0.0">foo </span>
  <span data-reactid=".1gm29bnrabk.1.0.1">bar</span>
  <span data-reactid=".1gm29bnrabk.1.0.2"> </span>
  <span data-reactid=".1gm29bnrabk.1.0.3">baz</span>
</span>

Makes absolutely no sense to me. I’ll just go ahead and assume that this makes the life of whoever maintains the virtual DOM implementation in React easier, but they should’ve considered using text nodes. You might think this is not a big deal, but it is if we’re styling <span> elements in a certain way. In particular if you just expected the span you actually wrote to be the only <span>, and defined a style such as font-size: 1.1em, or even padding: 5px.

Besides, isn’t the whole point of React to avoid as many DOM operations as possible?

Using conditionals in your view components

Whenever you’re dealing with dynamic representations of data, particularly optional user-entered information such as the metadata about a product in an e-commerce application, you’ll want some sort of way to conditionally render a component.

For some reason, React’s JSX make this unnecessarily complicated by not being able to use if statements inside code blocks. The advertised solution is to place your conditionals outside of the template, in the component’s render method. Here’s a piece of code showing the coding style they recommend.

var loginButton;
if (loggedIn) {
  loginButton = <LogoutButton />;
} else {
  loginButton = <LoginButton />;
}

return (
  <nav>
    <Home />
    {loginButton}
  </nav>
);

Note that, in case you just wanted the if leg (no else), you could leave loginButton as undefined and nothing would be rendered. The problem here is that you end up having to hoist parts of your component for no reason other than what is, plainly put, a limitation of the JSX language.

A (still terrible) workaround that allows you to place conditional logic into the template is to use binary operators. For some other reason, those are supported by JSX. Of course, this is sad to look at, slightly confusing, and not as clear as plain if / else would have been.

return (
  <nav>
    <Home />
    { loggedIn && <LogoutButton /> || <LoginButton /> }
  </nav>
);

Declaring a doctype

Apparently declaring a <doctype> is impossible when it comes to React. I ended up with an unimpressive '<!doctype html>' + layout concatenation. This would be fine if we were just doing client-side rendering but we’re trying to get to a shared rendering application here, kind of the whole point of using a library like React.

Concatenating the <doctype> on the server-side will have to do for now. Luckily, it doesn’t break the diffing algorithm when the client-side code boots the application state.

Declaring HTML comments

HTML comments are similarly hard to shove into a JSX template. Granted, this isn’t something you want to add to a document very often, but it’s still important to be able to declare some when you actually need to. If you want HTML comments in your JSX very badly, you can use the method below (derived from this blog post).

var comments = `<!--[if lte IE 8]>
  <script src="/js/html5shim.js"></script>
<![endif]-->`
<head dangerouslySetInnerHTML={{__html: comments}} />

Which, OBVIOUSLY brings us to my next point.

React’s “smart” dangerouslySetInnerHTML API

I definitely can relate to the notion of attempting to help your consumers not to make mistakes, but this API borders on pompous and presumptuous. The documentation rightly asserts that misuse of unescaped HTML might result in XSS vulnerabilities and that you should sanitize user input. A public API method’s name is not the place where to tell people everything that could go wrong with it, though.

Angular used to have a similar ng-bind-html-unsafe, which at least wasn’t as presumptuous. It has since been deprecated in favor of ng-bind-html, which is a saner version of React’s implementation. When passed a string, it’ll become sanitized, and if you want it to be unescaped you just tell it through their $sce.trustAsHtml(string) service. Of course, in that respect it might’ve been better to just let the user pass in something like { unescaped: 'some <strong>html</strong' } instead of having to go through a service, but it’s good enough – at least it’s not as condescending, and I don’t feel like throwing up whenever I’m using the API.

Components coupled to client-side code and ES6

Another big issue in my understanding of React is the way how components are loaded. Pretty much the entire React codebase is loaded on the server-side, and that means that a lot of libraries which are meant for the client-side of your application will be loaded on the server. This becomes an issue whenever you are loading a module that accesses the DOM before you even use it, – usually happens when a library does feature testing to decide the API they’ll export – as that’ll result in a thrown exception on the server-side (as there is no DOM to be accessed).

You could work around this by using require statements instead of import, and requiring those client-side-only modules on a method like componentDidMount, which only get executed in the client-side. ES6 import statements need to be defined at the top level of your module definition, though, which means you can’t work around this one using plain ES6.

This can be really problematic as even a test like modern = !!window.document.addEventListener would break on the server-side, and there’s plenty of client-side libraries that do this before you call any methods on them.

Wrapping Up

I’ve yet to play around with redux and client-side routing – two big things that I’ll be playing with next and which might change my mind about a few of the points I’ve made here. I’ve only played around with React lightly, so take the article with a grain of “React onboarding experience areas of improvement” salt.

The new site is now up at bevacqua.io, and while I definitely didn’t need to use React to put it together, it’s always interesting to try out new pieces of front-end technology. I don’t only have ranty things to say about JSX, and I’m sure it’ll grow on me as I use it for more stateful applications and combined with redux.

ES6 has been fun so far, and I find myself gradually adopting some of the language features, as well as writing less semicolons. What’s hard sometimes is to decide when to use an ES6 feature such as arrow functions over a named function declaration. In general I’ve been taking the approach of defaulting to the ES5 alternative, because I don’t want to be littering my code with ES6 expressions just for the sake of it.

As a fun fact, here’s a snippet of code I felt happy to write using lots of ES6 and the experimental :: ES7 function bind syntax. Usually there’s lots of named method involved in this sort of code, or async.apply (or contra.curry), and ES6 definitely made my code cleaner on this one.

concurrent({
  repo: next => query('/repos/' + repo, next),
  master: next => query('/repos/' + repo + '/branches/master', next)
}, ::this.pulledRepo)

By the way concurrent is a method from contra, equivalent to async.parallel.

Yay!

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments