Full-stack Web Technologies

CHAPTER 2
JSX

Generating pieces of the UI

Let's suppose we want to "paint" (or render) a user, consisting of a name and an age, to make it visible to a user in his browser. What are the options?

Generating DOM directly

In the browser, to create a piece of UI from data would involve writing a function like this:

const renderUserAsDOM = (name: string, age: number) => {
  const div = document.createElement("div");
  const span1 = document.createElement("span");
  span1.textContent = name;
  const span2 = document.createElement("span");
  span2.textContent = String(age);
  div.append(span1, span2);
  return div;
};

This is a lot of effort, since each node is created and filled with the data manually. Also, the code is not clear: it is difficult to see the created DOM tree in your head, you have to follow execution and imagine things as they happen, which involves mental effort.

Server Rendering

In the server, since we are supposed to send HTML, we could do it more clearly like this:

const renderUserAsHTML = (name: string, age: number) => {
  return `<div>
    <span>${name}</span>
    <span>${age}</span>
  </div>`;
};

This version is much easier to read, since the HTML code is like a template, the holes of which we fill with the data. The function is shorter and much nicer to read.

Generating Virtual DOM

To render in the browser, but at the same time retain the clarity of the HTML version, React invented JSX, a way to embed HTML inside Javascript.

So a function which generates React's Virtual DOM (instead of constructing DOM nodes or returning HTML) would look like this:

const renderUserAsJSX = (name: string, age: number) => {
  return (
    <div>
      <span>{name}</span>
      <span>{age}</span>
    </div>
  );
};

At first sight renderUserAsJSX and renderUserAsHTML seem almost the same. But the big difference between them is that the result of the JSX function is a tree of Virtual DOM, whereas the second is just a piece of text (well, HTML). To turn the HTML generated by renderUserAsHTML into DOM or Virtual DOM we would have to parse it again (which is more work).

JSX

JavaScript EXtended is a way to embed an HTML-like representation within Javascript code that is compiled into function calls (which look a lot like the DOM version, by the way).

JSX Syntax

  1. One JSX element is a Javascript expression, so you can put JSX expressions anywhere a Javascript expression can go:

    let a = <h1>Title</h1>;
    const getLink = () => <a href="http://www.google.com">Google</a>;
    let b = [<p>hi</p>, <div>hi</div>, <span>hi</span>];
    let obj = {
      first: <em>emphasize</em>,
      second: <strong>strong</strong>,
    };
    

    Every such expression has to have a root element, so things like these do not work:

    let twoElems = <div>a</div><div>b</div>; // ERROR! Two expressions!
    
  2. Holes marked with "{" and "}" allow insertion of Javascript values. The holes can happen in between text, as children of an element:

    let a = 13;
    let div = <div>I have {a} apples</div>;
    

    or then can also substitute an attribute (such as url in this case):

    const makeLink = (text: string, url: string) => {
      return <a href={url}>{text}</a>;
    };
    

    Holes can contain expressions, not just single values:

    let a, b;
    let msg = (
      <p>
        {a} multiplied by {b} is {a * b}
      </p>
    );
    
    let y = 1;
    let e = (
      <div>
        {y} year{y > 1 ? "s" : ""}
      </div>
    );
    
  3. Close all elements: 1) non-empty elements with its closing tag (<span></span>); 2) put a self-closing tag in empty elements:

    <br />
    <img src="..." alt="..." />
    <meta ... />
    
  4. class is rewritten to className: since class is a reserved word in Javascript, we need to use an alternative, so className was introduced:

    const User = (name: string, age: number) => (
      <div className="user">
        <span className="name">{name}</span>
        <span className="age">{age}</span>
      </div>
    );
    
  5. Comments have to be surrounded by { and }, and must be multi-line (VSCode already inserts them when pressing Ctrl+K Ctrl+C).

    <div className="user">
      {/* Here the name */}
      {/* Here the age */}
    </div>
    

Special Values

  1. Any Boolean or "falsy" values (the ones which convert to false), are removed:

    <div>He said {false}</div>     // <div>He said</div>
    <p>My name is Peter {null}</p> // <p>My name is Peter </p>
    <span>a is {undefined}</span>  // <span>a is </span>
    <em>b is {true}</em>           // <em>b is </em>
    
  2. Arrays just become sequences of expressions. That is, e1 and e2 will have exactly the same value here.

    let A = [1, "ho", null, 0.01];
    let e1 = <p>{A}</p>;
    let e2 = (
      <p>
        {1}
        {"ho"}
        {null}
        {0.01}
      </p>
    );
    

    Array values can be JSX expressions as well:

    let A = [<p>1</p>, <p>2</p>, <p>3</p>];
    let e = <div>{A}</div>; // <div><p>1</p><p>2</p><p>3</p></div>
    
  3. In arrays, every child must have a key. This lets React identify children when they move in the Virtual DOM (to be able to not re-render them):

    let people = [
      { id: 1, name: "pauek" },
      { id: 2, name: "julek" },
    ];
    <div>
      {people.map((p) => (
        <p key={p.id}>{p.name}</p>
      ))}
    </div>;
    

    An error message is generated in the console when this rule is broken.

Fragments

  1. When one needs to return two JSX elements a common way to achieve it is to wrap the elements in a <div>:

    const twoElems = () => (
      <div>
        <p>1</p>
        <p>2</p>
      </div>
    );
    

    But maybe we don't want an extra <div> if we insert those two elements in another list:

    <div className="top">
      {twoElems()}
      <p>3</p>
      <p>4</p>
    </div>
    

    The solution is to use a Fragment:

    const twoElemsFrag = () => (
      <>
        <p>1</p>
        <p>2</p>
      </>
    );
    

    which will disappear when inserted in another tree.

    The Fragment element can be used explicitly if you need to put a key on it:

    import React from "react";
    
    const twoElemsFragKey = (key: string) => (
      <React.Fragment key={key}>
        <p>first</p>
        <p>second</p>
      </React.Fragment>
    );
    
    <div>{["a", "b"].map((x) => twoElemsFragKey(x))}</div>;