“monitor showing C++” by Dlanor S on Unsplash

JSX — Up Close

Precious Nyarko
10 min readOct 29, 2018

If you write React code, then you no doubt, spend quite a bit of time, looking at, and writing JSX — perhaps, so much so, that you barely pause to give it a thought. But do you ever wonder why it works the way it does? Or why React must be in scope for it to work? Or why failing to wrap adjacent JSX elements in an enclosing tag, throws an error?

I could go on and on, but my point is that it’s often very helpful to know what’s happening under the hood, because it solidifies your understanding of how things work, and how to fix them when they don’t. Plus it makes you appreciate the tools you use even more. This post is an attempt to do just that!

A world without JSX

To appreciate what JSX is really doing for us, we have to first attempt to live in a world with JSX. To do that, it is helpful to remember that when a component renders it returns a tree of React elements — keyword here, being elements.

Without JSX the only other way to create a react element is to use the React.createElement(component, props, ...children) function. Let’s look at a simple example to see how it works:

Say we wanted to output a basic HTML form element to the DOM. Perhaps something like so <form class="login-form"></form>. Using React.createElement() we’d something like so:

React.createElement(
"form",
{ className: "login-form" }
);

At first glance that might not seem so bad, but that’s because our form currently is doing anything useful. Now, say we wanted to add two text inputs and a button to allow users log into our app. The code would now become:

function hello() {
return React.createElement(
"form",
{ className: "login-form" },
React.createElement(
"div",
{ className: "input-group" },
React.createElement("input", {
type: "text",
value: "",
placeholder: "Email Address"
})
),
React.createElement(
"div",
{ className: "input-group" },
React.createElement("input", {
type: "password",
value: "",
placeholder: "Password"
})
),
React.createElement(
"button",
{
type: "submit"
},
"Login"
)
);
}

Now, mind you, all of the code above was needed to render something, as basic as, a couple of inputs in a form. Now try to imagine, for a moment, expressing more complex UI with this approach. Now, scratch that! Could you imagine writing your entire application’s UI like this?

Not only would trying to write a React application without JSX make for a very unpleasant developer experience. It would be extremely time consuming and will very likely turn into a spaghetti code mess, very quickly.

Enter JSX

JSX is a syntax extension to JavaScript. Basically, it produces React elements and provides us a sweeter, more compact way to describe what our UI should look like.

Put simply, JSX is just a shorthand way of writing React.createElement(). It basically gets compiled down into React.createElement() calls.

If you take a moment to mull over this, you now see why attempting to use JSX without having first imported React, produces an error. Basically, for us to be able to call React.createElement(), React must first be in scope.

Armed with this knowledge, it also becomes clear why something like this wouldn’t work:

return (
<p>A paragraph!</p>
<p>Another paragraph!</p>
);

Since both JSX expressions boil down to React.createElement() calls, you can see that we are essentially providing more than one return value, which won’t work. Hence the need to wrap them in an enclosing tag.

With that out of the way, let’s explore the the goodies that JSX has to offer.

Expressions in JSX

In the example below, we declare a variable called age and then use it inside JSX by wrapping it in curly braces:

const age = 28;
const element = <h1>Hello, I'm {age} year's old</h1>;
ReactDOM.render(element, document.getElementById('root'));

You can put any valid JavaScript expression inside the curly braces in JSX. For example, !booked, user.firstName, or formatName(user) are all valid JavaScript expressions.

In the example below, we embed the result of calling a JavaScript function, formatName(user), into an <h1> element.

function formatName(user) {
return user.firstName + ' ' + user.lastName;
}

const user = { firstName: 'John', lastName: 'Doe' };
const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);

ReactDOM.render(element, document.getElementById('root'));

Note that, if statements and for loops are expressions in JavaScript, so can’t be used inside JSX directly, but are fine when used in the surrounding code.

JSX is an Expression Too!

Because JSX expressions compile to regular JavaScript function calls, you can use JSX inside of if statements and for loops, assign it to variables, accept it as arguments, and return it from functions:

function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}

Specifying Attributes

You may use either quotes (for string literals) or curly curly braces (to embed a JavaScript expression) when specifying the value of attributes in JSX.

<input placeholder="Enter your name" />; // quoted string literal
<img src={user.avatarUrl} /> // expression

Also, remember to not put quotes around curly braces when embedding a JavaScript expression in an attribute. You should either use quotes (for string values) or curly braces (for expressions), but not both in the same attribute.

<img src="{user.avatarUrl}" /> // don't do this

Specifying Children with JSX

JSX tags may contain children:

const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);

If a tag is empty, you may close it immediately with />:

const element = <img src={user.avatarUrl} />;

Specifying The React Element Type

The first part of a JSX tag determines the type of the React element.

Capitalised types indicate that the JSX tag is referring to a React component. These tags get compiled into a direct reference to the named variable, so if you use the JSX <Foo />expression, Foo must be in scope.

Choosing the Type at Runtime

You cannot use a general expression as the React element.

For times when you don’t know the React element type ahead of time — say you want render a different component based on a prop — you should assign the expression that determines the type, to a capitalized variable first — and then used the variable name as the React element type in your JSX.

import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// Wrong! JSX type can't be an expression.
// return <components[props.storyType] story={props.story} />;
// Correct! JSX type can be a capitalized variable.
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}

User-Defined Components Must Be Capitalized

When react encounters an element type starting with a lowercase letter, it thinks that the element it refers to is an HTML tag. So this example wouldn’t work as expected.

import React from 'react';

// Wrong! This is a component and should have been capitalized:
function hello(props) {
return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
// Wrong! React thinks <hello /> is an HTML tag because it's not capitalized:
return <hello toWhat="World" />;
}

As a result, you should capitalize your custom component names. Types that start with a capital letter like <Bar /> compile to React.createElement(Bar) and correspond to a component defined or imported in your JavaScript file.

import React from 'react';

// Correct! This is a component and should be capitalized:
function Hello(props) {
// Correct! This use of <div> is legitimate because div is a valid HTML tag:
return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
// Correct! React knows <Hello /> is a component because it's capitalized.
return <Hello toWhat="World" />;
}

Props

Props Default to “True”

If you pass no value for a prop, it defaults to true. These two JSX expressions are equivalent:

<MyTextBox autocomplete /><MyTextBox autocomplete={true} />

The React docs discourage this, because it can be confused with the ES6 object shorthand {foo} which is short for {foo: foo} rather than {foo: true}. This behavior is just there so that it matches the behavior of HTML.

Spread Attributes

Do you already have props as an object, and you want to pass it in JSX? That’s dead simple too. You can use ... as a “spread” operator to pass the whole props object. These two components are equivalent:

function App1() {
return <Greeting firstName="Ben" lastName="Hector" />;
}
function App2() {
const props = {firstName: 'Ben', lastName: 'Hector'};
return <Greeting {...props} />;
}

Tip: Mind you, JSX isn’t valid JavaScript. So while they look the same, the ... in the example above, is not JavaScript spread operator, but rather, JSX’s equivalent of it.

You can also pick specific props that your component will consume while passing all other props using the spread operator.

const Button = props => {
const { kind, ...other } = props;
const className = kind === "primary" ?
"PrimaryButton" : "SecondaryButton";
return <button className={className} {...other} />;
};
const App = () => {
return (
<div>
<Button kind="primary" onClick={() => console.log("clicked!")}>
Hello World!
</Button>
</div>
);
};

In the example above, the kind prop is safely consumed and is not passed on to the <button> element in the DOM. All other props are passed via the ...other object making this component really flexible. You can see that it passes an onClick and children props.

While useful, spread attributes make it easy to pass unnecessary props to components that don’t care about them or to pass invalid HTML attributes to the DOM. So, use this syntax sparingly.

Children

In JSX expressions that contain both an opening tag and a closing tag, the content between those tags is passed as a special prop: props.children. There are several different ways to pass children — namely string literals, JSX children and JavaScript expressions.

String Literals

As we’ve seen before, you can put a string between the opening and closing tags and props.children will just be that string. This is useful for many of the built-in HTML elements. For example:

<MyComponent>Hello world!</MyComponent>

This is valid JSX, and props.children in MyComponent will simply be the string "Hello world!". HTML is unescaped, so you can generally write JSX just like you would write HTML in this way:

<div>This is valid HTML &amp; JSX at the same time.</div>

Also worth noting, is that, JSX removes whitespace at the beginning and ending of a line. It also removes blank lines. New lines adjacent to tags are removed; new lines that occur in the middle of string literals are condensed into a single space. So these all render to the same thing:

<div>Hello World</div><div>
Hello World
</div>
<div>Hello World</div>

JSX Children

You can also provide more JSX elements as the children. This is useful for displaying nested components

<MyContainer>
<MyFirstComponent />
<MySecondComponent />
</MyContainer>

You can mix together different types of children, so you can use string literals together with JSX children. This is another way in which JSX is like HTML, so that this is both valid JSX and valid HTML:

<div>
Here is a list:
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</div>

A React component can also return an array of elements:

render() {
// No need to wrap list items in an extra element!
return [
// Don't forget the keys :)
<li key="A">First item</li>,
<li key="B">Second item</li>,
<li key="C">Third item</li>,
];
}

JavaScript Expressions as Children

You can pass any JavaScript expression as children, by enclosing it within {}. For example, these expressions are equivalent:

<MyComponent>foo</MyComponent><MyComponent>{'foo'}</MyComponent>

This really comes in handy, when rendering lists of things inn your application. This example renders an HTML list:

function Item(props) {
return <li>{props.message}</li>;
}
function TodoList() {
const todos = ['finish doc', 'submit pr', 'nag dan to review'];
return (
<ul>
{todos.map((message) => <Item key={message} message={message} />)}
</ul>
);
}

JavaScript expressions can be mixed with other types of children. This is often useful in lieu of string templates:

function Hello(props) {
return <div>Hello {props.addressee}!</div>;
}

Functions as Children

Normally, JavaScript expressions inserted in JSX will evaluate to a string, a React element, or a list of things. However, props.children works just like any other prop in that it can pass any sort of data, not just the sorts that React knows how to render. For example, if you have a custom component, you could have it take a callback as props.children:

// Calls the children callback numTimes to produce a repeated component
function Repeat(props) {
let items = [];
for (let i = 0; i < props.numTimes; i++) {
items.push(props.children(i));
}
return <div>{items}</div>;
}
function ListOfTenThings() {
return (
<Repeat numTimes={10}>
{(index) => <div key={index}>This is item {index} in the list</div>}
</Repeat>
);
}

Children passed to a custom component can be anything, as long as that component transforms them into something React can understand before rendering. This usage is not common, but it works if you want to stretch what JSX is capable of.

Booleans, Null, and Undefined Are Ignored

false, null, undefined, and true are valid children. They simply don’t render. These JSX expressions will all render to the same thing:

<div /><div></div><div>{false}</div><div>{null}</div><div>{undefined}</div><div>{true}</div>

This can be useful to conditionally render React elements. This JSX only renders a <Header /> if showHeader is true:

<div>
{showHeader && <Header />}
<Content />
</div>

One caveat is that some “falsy” values, such as the 0 number, are still rendered by React. For example, this code will not behave as you might expect because 0 will be printed when props.messages is an empty array:

<div>
{props.messages.length &&
<MessageList messages={props.messages} />
}
</div>

To fix this, make sure that the expression before && is always boolean:

<div>
{props.messages.length > 0 &&
<MessageList messages={props.messages} />
}
</div>

Conversely, if you want a value like false, true, null, or undefined to appear in the output, you have to convert it to a string first:

<div>
My JavaScript variable is {String(myVariable)}.
</div>

Parting words

I had hoped to leave you with a greater awareness and sense of appreciation of what JSX is really doing for you behind the scenes each time you use it. Hopefully, I’ve managed to do just that!

I hope you found this post useful. Any suggestions for improvements are very much appreciated !

--

--