Very Confusing differences between JSX and HTML

October 17, 2019 /

When I work with both JSX and HTML, I get tripped up by subtle differences between the two languages. In this post, I'll share the differences that I find Very Confusing.

πŸšΆπŸ½β€β™‚οΈ Before we get started, I want to clarify some definitions. You can skip this first half if you're already familiar with React! πŸƒπŸ»β€β™€οΈ


What is JSX (intuitively)?

JSX is the syntax that goes in your React components*.

*(You can use JSX with frameworks other than React, but that's beyond the scope of this post.)

import React from "react";

export const MyReactComponent = () => (
  <div>
    <h1>Hi there! This is My Component.</h1>
    <h2>Isn't it cool?</h2>
  </div>
);

The syntax after MyReactComponent = () => ( is JSX. It looks like HTML. That's intentional!

When you run your React app, the rendered component will look similar to what this raw HTML page would render:

<!DOCTYPE html>
<html>
  <body>
    <div>
      <h1>Hi there! This is My HTML page.</h1>
      <h2>Isn't it cool?</h2>
    </div>
  </body>
</html>

I learned HTML before JSX, so I use HTML as a mental model of how JSX works. I look at the JSX in my React component and imagine an equivalent HTML page. (I wonder if it's the other way around for developers who learned JSX first πŸ€”?)

Since JSX and HTML are so similar, in the few situations where they differ, my mental model model can be really deceptive 😱.

Ok but like what actually is JSX?

Fair question. When I say JSX in this post, I'm hand-wavily referring to JSX + React + ReactDOM + the DOM.

In the purest sense, JSX is just a language that compiles into JS. If you compile the code sample above for React with Babel, you'll get this equivalent JS code.

import React from "react";

export const MyReactComponent = () =>
  React.createElement(
    "div",
    null,
    React.createElement("h1", null, "Hi there! This is My Component."),
    React.createElement("h2", null, "Isn't it cool?")
  );

This isn't HTML in any sense. JSX never** gets "converted" to HTML, that's just a helpful mental model. Instead, JSX gets compiled to this tree of JS function calls.

**(I am kinda lying here again.)

At runtime, React magically transforms that tree into actual DOM elements. In other words, you can loosely imagine React as the "bridge" that gets your JSX to show up as actual DOM elements in the browser.

This behavior is similar to how your browser reads an HTML page and constructs the DOM based on that.

This is a diagram showing how JSX and HTML are transformed into elements the DOM. The DOM is what you "see" in your browser. JSX and HTML only look similar.
  a "JSX file containing a React component" gets compiled into a "JS file containing a React component." React updates the DOM at runtime.
  an "HTML file" gets constructed into the DOM by the browser.

In summary, JSX and HTML look similar as source code, and they create similar end results in the DOM.


Differences between JSX and HTML

Ok! With all those definitions out of the way πŸ˜…, I'm going to talk about the differences between JSX and HTML that Very Confuse me.

Boolean props

If you have this element in JSX, what do you think it does?

<button disabled />

Intuitive meaning:

Let's look at some other syntax to see if we can figure out what this means. There are two similar syntaxes, one in JS and one in HTML.

In JS, these two are equivalent:

const disabled = false; // or true
const props = { disabled };

// is equivalent to

const disabled = false; // or true
const props = { disabled: disabled };

In HTML, this syntax means disabled is true:

<button disabled />

Which one do you think JSX follows?

Actual meaning:

When debugging JSX, I often find it helpful to compile the JSX to JS. (Is that a sign of a leaky abstraction πŸ€”?) Let's compile it.

<button disabled />;

// compiles to

React.createElement("button", { disabled: true });

Turns out that JSX matches the HTML behavior. A prop with no specified value gets passed as true. This wasn't a difference between JSX and HTML after all.

When does this occur?

Usually I introduce boolean props accidentally when refactoring code. I'll start with something like this:

const props = {
  name,
  onClick: () => setName(name),
  placeholderText: "Enter your name",
};

<NameDisplay {...props} />;

and I'll decide to rewrite it without the {...props} spread:

<NameDisplay
  name
  onClick={() => setName(name)}
  placeholderText="Enter your name"
/>

But! I forget to expand name into name={name}.

Using ESLint or TypeScript is helpful in this situation, since it will warn me that I never used the variable name until I write name={name}.

There is a code sample:
  `const name = 'Daniel';`
  `<NameDisplay name />`
  Hovering my mouse over name, TypeScript shows me an error.
  'name' is declared but its value is never read. ts(6133)

Numeric props

Is this valid JSX?

<input maxLength="10" />

Sort of. It will work πŸ€·πŸΌβ€β™‚οΈ. However, if you're using TypeScript, you'll get a compilation error saying that you can't pass a string for maxLength.

There is a code sample:
  `<input type="text" maxLength="10" />`
  Hovering over maxLength, TypeScript is showing me an error.
  `maxLength?: number;`
  `(JSX attribute) InputHTMLAttributes<HTMLInputElement>.maxLength?: number | undefined`
  `Type 'string' is not assignable to type 'number | undefined'. ts(2322)`
  `index.d.ts(1965, 9): The expected type comes from the property 'maxLength' which is declared here on type 'DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>'`

This is different from HTML, where maxlength="10" is acceptable syntax.

ℹ️ attributes and props are analogous:
  • HTML elements have attributes.
    • e.g., maxlength in <input maxlength="16" />
  • JSX elements have props.
    • e.g., maxLength in <input maxLength="16" />

In HTML, all attributes are strings (well, close enough). So if you want to set an attribute to a number, you set maxlength="10" and internally the DOM will transform that "10" string into a number.

JSX, however, supports multiple ways of passing props. There's maxLength="10" and maxLength={10}.

  • maxLength={10} passes the JS expression between {}, which is 10 in this case.
  • maxLength="10" passes a string.
    • You can think of it as shorthand for maxLength={"10"}.

Let's try compiling those.

<input maxLength="10" />;
// compiles to
React.createElement("input", {
  maxLength: "10",
});

<input maxLength={10} />;
// compiles to
React.createElement("input", {
  maxLength: 10,
});

<input maxLength={"10"} />;
// compiles to
React.createElement("input", {
  maxLength: "10",
});

You can imagine that React.createElement("input", ...) has a type definition that only accepts certain props:

interface InputElementProps {
  maxLength?: number;
}

Which means that { maxLength: "10" } would fail the type check, like we saw in the original error message for <input maxLength="10" />.

Self-closing tags

A self-closing tag is a tag that doesn't need a corresponding closing tag.

The syntax ends with />, like <div />.

The behavior of the /> syntax is pretty different between JSX and HTML. I've found this difference most confusing when I've been working with JSX for a while, then go back to editing a legacy HTML template, since I'll expect the HTML to behave the same way JSX does. My HTML page won't throw a compilation error to tell me I did it wrong, either πŸ˜”.

JSX

In JSX, the <div /> syntax is equivalent to <div></div>.

It's the same behavior, regardless of the element's type. (div, br, MyComponent, circle, ...) That's it. Try it out in your compiler and see that they compile to the same thing.

HTML

...It's complicated.

First off, if you look back at XHTML (we don't talk about XHTML) or HTML4, the behavior is different. I'll ignore this fact and only cover modern HTML (i.e., HTML5, documents that start with <!DOCTYPE html>).

In HTML, the /> syntax is usually meaningless, but it depends on specifically what element you use it on.

I wouldn't recommend memorizing that, or anything. Just be aware that the /> syntax in HTML is haunted and in JSX it is simple πŸ˜‚. And also, if you write a blog post and start referencing the HTML spec, you'll have a bad time writing out all the edge cases.

Conclusion

There are plenty more differences between JSX and HTML, and I recommend reading the official React documentation for a more broad overview: JSX in Depth. If you're interested in differences between React's DOM and the DOM, check out DOM Elements.

Anyway. It's a scary world out there. Be careful!

Back to home / Discuss on Twitter / Source on GitHub