CHAPTER 2JSX
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
-
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!
-
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> );
-
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 ... />
-
class
is rewritten toclassName
: sinceclass
is a reserved word in Javascript, we need to use an alternative, soclassName
was introduced:const User = (name: string, age: number) => ( <div className="user"> <span className="name">{name}</span> <span className="age">{age}</span> </div> );
-
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
-
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>
-
Arrays just become sequences of expressions. That is,
e1
ande2
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>
-
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
-
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>;