Skip to content

JSX: The Confusing Parts


This article doesn't attempt to explain every single aspect of JSX, but rather to explain the parts that most people find difficult to grasp when they first learn it. Namely: What is JSX and Why is it weird sometimes?

Here is the official React documentation on JSX.

What is JSX?

JSX is an XML-based syntax for building DOM elements in JavaScript. It looks like this:

// This is supposed to be JavaScript, yet the value is HTML-like
const myDiv = <div>Hello World</div>

Notice that the value is not a string. There are no quote marks around it. In fact, this is not valid JavaScript at all. If we try to run this as a JavaScript file, the browser's engine will crash. The browser does not understand JSX and neither does the React API.

Let me explain. First, let's discuss Modern JavaScript Bundling.

Modern JavaScript Bundling

Since we're making applications in Front-End JavaScript now, our codebase is much bigger than it used to be. There are many files involved and thus a build step is needed to bundle them together. Without this step, an application might have 100s or 1000s of files which would need to be downloaded individually. One of the main roles of a bundler (like Webpack or Rollup) is to safely concatenate the files together to produce one file (or several files that are split up for application reasons). We get to author in many files that are organized for us to understand, but the end-result are build files that we put on production servers.

During the build process, plugins can be used to augment the code while it's being bundled. The most common plugin paired with React is Babel -- a JavaScript compiler which allows us to write JavaScript one way and Babel will rewrite it to be another way.

The reason we want Babel to rewrite our JavaScript is so that we can do things like use future features of JavaScript that browsers don't understand yet. Babel will rewrite our code to be compatible with what browsers currently understand. This makes it feel like we're writing future code and we don't have to wait for it to be ready in every browser.

We can also write in JSX which will be re-written to be valid JavaScript:

// You get to write this JSX
const myDiv = <div>Hello World</div>

// And Babel will rewrite it to be this:
const myDiv = React.createElement('div', null, 'Hello Workd')

React doesn't deal with JSX directly. The React API expects to see React.createElement and not JSX at all. The idea is to have Babel transpile the JSX we write before React gets it.

We could write all our React code with React.createElement instead of using JSX if we wanted to. For this reason, JSX is said to be "Syntax Sugar", a term that means we can write in a nicer syntax instead of an alternative which isn't so nice.

Babel's role is to intercept the files in the build process and to compile them accordingly. Their website even lets you play around with example code to see how it would compile

JSX is not a Template Language

At first, JSX seems like a template language similar to others that have existed. But it's very important to remind yourself that JSX is JavaScript with a different syntax.

In JSX we can use curly braces to inject variables:

// What we write
const message = 'Hello World'
const myDiv = <div>{message}</div>

// And Babel creates:
const message = 'Hello World'
const myDiv = React.createElement('div', null, message)

With a template language, this would be a way to do string interpolation. But that's not what's going on in JSX. The use of curlies lets us open JSX to the power of plain JavaScript. Instead of thinking of it as interpolation, think of it as passing an argument because that's exactly what you're doing.

When we write JSX, we can pass content into the body of the tag (between the opening and closing tag) which becomes the third argument to React.createElement. In fact, lets prove it by nesting elements:

// We write this:
const message = 'Hello World'
const myDiv = <div><span>{message}</span></div>

// Babel transpiles to:
const message = 'Hello World'
const myDiv = React.createElement('div', null,
  React.createElement('span', null, message)
)

The div gets turned into one React.createElement where the body of the div gets passed into the third argument. That argument is a span so it becomes it's own React.createElement. If we choose to not write JSX and to write React.createElement directly, we're going to end up with a not of nesting!

Another important takeaway is the part where the curly braces allows us to write JavaScript. We can do simple variables like message from above, or we can do more complex expressions:

// Some function that returns an array
const getMessage = function() {
  return ['Hello World']
}

// When we write this:
const myDiv = <div><span>{getMessage()[0]}</span></div>

// Babel changes it to this:
const myDiv = React.createElement('div', null,
  React.createElement('span', null, getMessage()[0])
)

The fact that we can call a function and then immediately use it's return array like this getMessage()[0] is not React at all -- it's actually just JavaScript.

Whitespace

Whitespace in HTML is something that either causes us problems or helps us out depending on how we look at it. Consider this HTML file:

<!-- HTML -->
<body>
  <span>Hello</span>
  <span>World</span>
</body>

When this file is rendered, the output will look like this with a space and no hard return:

Hello World

Now consider this HTML:

<!-- HTML -->
<body>
  <span>Hello</span><span>World</span>
</body>

In this case, the output will be "HelloWorld" without a space. In the first example, the space comes from the hard return between the two spans. In HTML, hard returns are reduced to just spaces for visual purposes. But we're not writing HTML, we're writing JSX which is JavaScript which React will send to the DOM for us. For that reason, the way we write our JSX is going to have a different effect vs writing plain HTML. It doesn't matter if we use hard returns or not between JSX elements, React is going to remove them. If this example were HTML, we would see a space, but since it's JSX it will be rendered as if the spans were on the same line:

const body = (
  <body>
    <span>Hello</span>
    <span>World</span>
  </body>
)

This is an example of what React would have made from the example above:

<!-- HTML Output -->
<body><span>Hello</span><span>World</span></body>

If we specifically want a space between the spans, we must tell React by doing this:

const body = (
  <body>
    <span>Hello</span>{' '}
    <span>World</span>
  </body>
)

It's a little weird but don't worry, you probably won't need to do this often. Chances are you'll solve most of your spacial issues with styling (margin and padding). However, the problem does arise when you have inline elements in large bodies of text.

Read the actual paragraph tag to see why:

const paragraph = (
  <p>
    Here is a long paragraph. For readability, you
    probably want to do hard returns so the content
    doesn't go off the edge of your screen. In normal
    HTML these hard returns aren't a problem because
    the browser treats the hard return as if it were
    a space. It's not a problem either in JSX until
    you decide you want to <b>end or start a line</b>
    with another element. In this case, the word 
    "line" and "with" will be touching (they won't
    get a space). To solve this, elements added to
    the end or beginning of a line should have the
    {' '}<b>curly brace trick</b> to ensure a space
    is created. Cheers!
  </p>
)

Prop Values

JSX was created to resemble HTML, but with the power of reusable components. By returning JSX from a function, we create that re-usability:

function User(props) {
  return <div>User: {props.name} is {props.age}</div>
}

// When a function (with a capital letter) returns JSX,
// it is said to be a component. As such, it can be used
// like an element like this:

function App() {
  return (
    <div>
      {/* These are two User elements */}
      <User name="Dave" age="34" />
      <User name="Julie" age="25" />
    </div>
  )
}

Babel transpiles the <User /> element like this:

// Notice the first argument is a reference to the function instead
// of a string like it would be with a DOM based element (like 'div')
React.createElement(User, { name: "Julie", age: "25" })

We can see how the props are converted to an object. This object is passed as the first argument to the User function.

In the above example, we're passing string props. If we want to pass a value other than a string, we do curly braces:

<User name="Julie" age={25} />

Babel now transpiles to:

React.createElement(User, { name: "Julie", age: 25 })

By using curly braces, we can do anything in JavaScript that resolves to a value:

<OurComponent
  someCallback={() => {}}
  someValue={myArray[0].split(' ')}
/>

// Babel transpiles it to:
React.createElement(User, {
  someCallback: () => {}, // An Arrow Function
  someValue: myArray[0].split(' ')
})

Here's how we pass in values of different types:

<OurComponent
  name="Brad"                   // String
  age={34}                      // Number
  retired={false}               // Boolean
  trainer={true}                // Boolean
  hobbies={[                    // An array
    'Computer Stuff',
    'Long walks on the beach'
  ]}
  location={{                   // An Object
    state: 'Arizona',
    country: 'US'
  }}
  onRemove={removeUser}         // A reference to a function
  onSave={() => {               // An inline arrow function
    console.log('Hello')
  }}
/>

Pay special attention to arrays and objects, notice that the first set of curlies is telling JSX that we want to do some JavaScript. Then we can do square brackets for arrays or another set of curlies for an object literal.

Booleans that are true can be written without a value, similar to HTML:

// This is the exact same as doing trainer={true}
<OurComponent trainer />

Open or Closed Tags?

Which of these two is correct?

// This:
<OurComponent />

// Or this:
<OurComponent></OurComponent>

They're actually both correct. Both in the example do the exact same thing. In fact, you can even write self-closing DOM elements like this: <div /> which is valid JSX.

Children Props

If you want, you can pass a special kind of prop into the element called children:

<User age={56}>Brad</User>

We don't see that word children as a prop "attribute". Instead it's the body between the opening and closing tags. Whatever we put there is received as props.children. Here's how that User component might look:

function User(props) {
  // props.children is "Brad"
  return <div>User: {props.children} is {props.age}</div>
}

Although it's not common to do, the children prop can be passed like this too:

<User age={56} children="Brad" />

However, the conventional purpose of using children is to pass in more JSX:

// This is easy to read:
<User age={56}>
  <span>Brad</span>
</User>

// This works the same, but not as easy to read:
<User age={56} children={<span>Brad</span>} />

Classname

For the time being, we have to use className instead of class. This has to do with the fact that class is a reserved word in JavaScript:

// This JSX
<div className="active">Hello</div>

// Turns into this HTML
<div class="active">Hello</div>

There is some chatter about refactoring JSX to allow class instead, but it's not built into React at the time of this writing.

Map and Keys

Let's say you want to make a list of users from an array:

function UserList() {
  const users = ['Michael', 'Ryan']

  return (
    <ul>
      <li>{users[0]}</li>
      <li>{users[1]}</li>
    </ul>
  )
}

The two li elements are going to become the children props to the ul element. React needs children to be one thing though, so internally React makes this an array. We could mimic that by doing this:

function UserList() {
  const users = ['Michael', 'Ryan']

  return (
    <ul>
      {[
        <li>{users[0]}</li>,
        <li>{users[1]}</li>
      ]}
    </ul>
  )
}

This is just to demonstrate that we can do this, not that we would. We probably want to write JSX that more closely resembles HTML. But just keep in mind that we could do this.

Right now the code isn't very dynamic. Instead of hardcoding the user's array let's pass it through props where the element is invoked. Then we can iterate over the values using .map:

function UserList(props) {
  return (
    <ul>
      {props.users.map(name => {
        return <li>{name}</li>
      })}
    </ul>
  )
}

<UserList users={['Michael', 'Ryan']} />

Iterating with .map is perfect because we need to convert one array to another array -- an array of strings to an array of JSX. It's also perfect because .map returns a value. Remember, we need to pass a value into the curlies of JSX. This is the reason why we can't use .forEach because it does not return a value. This is also why we can't use things like while, if, switch, for, or forEach in JSX.

Even though the above examples work, we're missing something called a key.

When we write JSX and React build the DOM for us, there's a reconciliation process that React uses to track which of our components go with which parts in the DOM. React knows how to take care of that for us except in cases where we provide JSX as an array (which is what we're doing when we do .map). Therefore, we have to supply a unique key so React can track things:

// .map supplies the index as the second argument of
// the callback, which we can use for a key:
function UserList(props) {
  return (
    <ul>
      {props.users.map((name, index) => {
        return <li key={index}>{name}</li>
      })}
    </ul>
  )
}

A few notes on keys:

  • The key just needs to be unique for the array, not for the whole component or the whole application. If you had one component that made a list of users and a list of pets, each list could use the index from the array and that's fine.
  • Keys can be numbers or strings, as long as they're unique.
  • You may hear that using indexes of the array is a bad idea. That's only true if the list is going to change while the component is still mounted. Imagine if we used indexes and our array had 3 items then one was removed. If the first item in the array was removed, then what used to be at index 1 is now at index 0. React will be confused because it already had a key called 0 and the reconciliation process will not work correctly. This is why we tend to use database IDs instead. Chances are your list isn't as simple as the one from above and it comes from a database so this shouldn't be a problem.

Return one JSX node

React can only accept one JSX node returned from a component:

// This is not allowed because we're returning three things:
function UserList() {
  return (
    <div>Dave</div>
    <div>Julie</div>
    <div>Sam</div>
  )
}

// This is allowed since the wrapping div is the one thing
// returned, regardless of how many children things it has:
function UserList() {
  return (
    <div>
      <div>Dave</div>
      <div>Julie</div>
      <div>Sam</div>
    </div>
  )
}

If you think about how Babel would convert the incorrect example, it would look like this:

function UserList() {
  return React.createElement("div", null, "Dave")
         React.createElement("div", null, "Julie")
         React.createElement("div", null, "Sam");
}

This is not valid JavaScript, therefore it isn't valid JSX. The second example with the wrapping div would make this which is valid JavaScript:

function UserList() {
  return React.createElement("div", null,
    React.createElement("div", null, "Dave"),
    React.createElement("div", null, "Julie"),
    React.createElement("div", null, "Sam")
  );
}

Fragments

Sometimes you want to return sibling JSX nodes without having to arbitrarily wrap them in another tag which means another DOM element just to satisfy a React circumstance.. For that situation, there are Fragments:

import React from 'React'

function UserList() {
  return (
    <React.Fragment>
      <div>Dave</div>
      <div>Julie</div>
      <div>Sam</div>
    </React.Fragment>
  )
}

You might see Fragments written one of three ways. The above assumes you've only imported React. With ES Modules we can import the file's default export and any of its named exports at the same time:

import React, { Fragment } from 'React'

function UserList() {
  return (
    <Fragment>
      <div>Dave</div>
      <div>Julie</div>
      <div>Sam</div>
    </Fragment>
  )
}

We can also do this Fragment syntax which Babel transpiles to React.Fragment:

import React from 'React'

function UserList() {
  return (
    <>
      <div>Dave</div>
      <div>Julie</div>
      <div>Sam</div>
    </>
  )
}

// Note: this shorthand syntax is only available with modern versions of Babel

ES6 and JSX

We have another article that outlines what JavaScript things you need to know for learning React. Be sure to read the ES6 section there before this section.

This section should help you distinguish "What is React and what is JavaScript". When developers are new to React, they're trying to learn React itself, also this new JSX syntax which is confusing, then to add to the mix, every example online is using the most modern JavaScript which makes it hard to tell the difference between React things and JavaScript things. Here's a list of the most common things you'll see that aren't technically JSX or React, but they're related:

Arrow Functions

When we need a callback function in JSX and we want to inline it, it's very common to use an arrow function:

<button onClick={event => {
  console.log('Button Was Clicked)
}}>
  Click Me
</button>

Since props can receive long arrays, objects, or functions that look better with hard-returns, it's very common to see JSX that looks like this. It looks freaky at first when you're only accustomed to HTML. But it's just become apart of the React culture.

Object Destructuring

We destructure a lot in React. It's not required of course, but this code is a perfect example:

function User({ users }) {
  return (
    <ul>
      {users.map(({ id, name }) => {
        return <li key={id}>{name}</li>
      })}
    </ul>
  )
}

<UserList users={[
  { id: 23, name: 'Michael' },
  { id: 45, name: 'Ryan' }
]}>

Not only are we destructuring the props object in the arguments (so we can use users instead of props.users), but we're also taking the item that we're receiving from the .map and destructuring it. Note that users is an array of objects, therefore the first argument to .map is an object, therefore it can be destructured.

To take it one step further, if we're just returning a value from an arrow function then we can omit the return:

<ul>
  {users.map(({ id, name }) => <li key={id}>{name}</li>)}
</ul>

...spread and ...rest

You may see components like this:

function Button({ children, icon, ...rest }) {
  return (
    <button {...rest}>
      {icon && <Icon icon={icon} />}
      {children}
    </button>
  )
}

At the top, we see ...rest inside of destructuring. That's saying that of all the props we want children and icon to be separated into their own variables, but whatever is left put into an object called rest. Then the object called rest and "spread" it into the props of button.

Here's how spreading props works:

// Let's say we have this object
const me = { name: 'Brad', age: 76 }

// We can hand set the props of User:
<User name={me.name} age={me.age} />

// Or we can "spread" them:
<User {...me} />

Both of these <User> elements will have the same props: name and age.

The reason why spread was nice for the button example is because we want to allow someone to use our custom <Button> just as if it were a DOM <button>. We've just supercharged it so it can have icons, but anything else you pass to <Button> is going to be applied to <button>. Now we don't have to itemize each possible DOM attribute that could be passed in.


I hope you've enjoyed learning more about JSX!

Loading...

React Router

Michael Jackson and Ryan Florence create the React libraries that you use in your apps like React Router and Reach UI. All of our trainers are experts in React and JavaScript so let us share our knowledge with you and your team!

I Love React
© React Training 2019