Skip to content

Latest commit

 

History

History
399 lines (293 loc) · 12 KB

B03-jsx.md

File metadata and controls

399 lines (293 loc) · 12 KB

Exercise and Slides

One of the first things you probably noticed about React code is that it looks like the component function is returning HTML. This HTML-like syntax is actually called JSX.

What Is JSX?

JSX is a syntax invented for React that looks very similar to (X)HTML. It allows you to create elements by writing in a familiar-looking syntax, instead of writing out function calls by hand. The HTML-like syntax actually compiles down to real JavaScript.

Did you notice how there are no quotes around the “HTML”? That’s because it’s not a string. The lack of quotes is not just a trick, either. React is not parsing the tags and converting them into HTML.

It looks like HTML. In reality though, JSX is just a nice syntax for function calls that create DOM elements.

So what is React actually doing here? How does this work?

JSX Is Compiled to JavaScript

The JSX elements you write are actually compiled down to JavaScript by a tool called Babel. Babel is a compiler that transforms code into valid ES5 JavaScript that all browsers can understand, and it’s bundled in with projects created by Create React App.

After you run npm start, a tool called Webpack is watching for files to change. When they do, it feeds those files into Babel, which turns JSX into JS, and sends it to your browser via the development server running on port 3000.

Each JSX element becomes a function call, where its arguments are its attributes (“props”) and its contents (“children”).

Here’s an example of a simple React component that returns some JSX:

function Hello() {  return <span>Hello!</span>;}

And here is the JavaScript generated by the Babel compiler:

function Hello() {
	return React.createElement(
    'span',
		{},   
		'Hello!' 
	);
}

The React.createElement function signature looks like this:

React.createElement(
  string|element,  [propsObject],  [children...]
)

The string|element can be a string describing an HTML or SVG tag (like 'div' or 'span'), or it can be a component a.k.a. function (like HelloWorld, with no quotes).

The propsObject and children are optional, and you can also supply more than one child by passing additional arguments:

function HelloWorld() {
	return React.createElement(
    'div',
		{},
		'Hello',
		'World'
	);
}

Try it yourself! Rewrite the HelloWorld component to call React.createElement instead of returning JSX.

function HelloWorld() {
	return React.createElement(
    /* fill this in */  
	)
;}

Here is a slightly more complicated bit of JSX, and a preview of what’s to come. You can see that it references a function parameter named props. We haven’t talked about props yet, but this is the way you pass arguments to React components.

function SongName(props) {
	return (
    <span className='song-name'>
			{props.song.name}
		</span>
	);
}

And here is what it compiles to:

function SongName(props) {
	return (
    React.createElement('span',
			{ className: 'song-name' },
			props.song.name
		)
  );
}

See how JSX is essentially a nice shorthand for writing function calls? You don’t even have to use JSX if you don’t want to – you can write out these function calls manually.

Your first instinct might be to avoid writing JSX because you don’t like the look of “HTML in JS.” Maybe you’d rather write real JavaScript function calls, because it feels more “pure” somehow. I suggest giving JSX an honest try before you give up on it.

Writing out the React.createElement calls is not a common approach in the React community. Essentially all React developers use JSX, which means code that you see in the wild (on GitHub, Stack Overflow, etc.) is likely to be written with it.

Working With JSX

Composing Components

JSX, like HTML, allows you to nest elements inside of one another. This is probably not a big surprise.

Let’s refactor the HelloWorld component from earlier to demonstrate how composition works. Here’s the original HelloWorld:

function HelloWorld() {
  return <div>Hello World!</div>;
}

Leaving the HelloWorld component intact for now, create two new components: one named Helloand one named WorldHello should render <span>Hello</span> and World should render <span>World</span>. You can basically copy-and-paste the HelloWorld component and just change the text and the function name.

Go ahead and try making that change yourself. I’ll wait.

Got it?

It’s important to actually type this stuff out and try it yourself! Don’t just read while nodding along, because you won’t actually learn it that way. It’s very easy to look at code and think, “Yep, that all makes sense.” You’ll never know if you truly understand until you try it.

Your two new components should look like this:

function Hello() {
  return <span>Hello</span>;
}
function World() {
  return <span>World</span>;
}

Now, change the HelloWorld component to use the two new components you just created. It should look something like this:

function HelloWorld() {
  return (
    <div>
      <Hello /> <World />!
    </div>
  );
}

Assuming the app is still running, the page should automatically refresh. If not, make sure the app is running (run npm start if it’s not).

You should see the same “Hello World!” as before. Congrats!

I know this seems painfully simple, but there are some lessons here, I promise.

The next few examples depend upon the Hello and World components that you should have created above, so make sure those exist before you continue.

Wrap JSX with Parentheses

A quick note on formatting: you might notice I wrapped the returned JSX inside parentheses, (). This isn’t strictly necessary, but if you leave off the parens, the opening tag must be on the same line as the return, which looks a bit awkward:

function HelloWorld() {
  return <div>
      <Hello /> <World />!
   </div>;
}

Just for kicks, try moving the <div> onto its own line, without the surrounding parens:

function HelloWorld() {
  return 
		<div>
      <Hello /> <World />!
   </div>;
}

This will fail with an error.

If you look in the browser console, you’ll also likely see a warning about “Nothing was returned from render.”

This is because JavaScript assumes you wanted a semicolon after that return (because of the newline), effectively turning it into this, which returns undefined:

function HelloWorld() {
  return;
  <div>
    <Hello /> <World />!
  </div>;
}

So: feel free to format your JSX however you like, but if it’s on multiple lines, I recommend wrapping it in parentheses.

Return a Single Element

Notice how the two components are wrapped in a <div> in the HelloWorld example:

function HelloWorld() {
  return (
    <div>
      <Hello /> <World />!
    </div>
  );
}

Here’s a little exercise: try removing the <div> wrapper and see what happens. You should get this error:

Adjacent JSX elements must be wrapped in an enclosing tag.

If this seems surprising, remember that JSX is compiled to JS before it runs:

// This JSX:
function HelloWorld() {
  return (<Hello/> <World/>);
}

// Becomes this JS:
function HelloWorld() {
  return (
    React.createElement(Hello, null)
    React.createElement(World, null)
  );
}

Returning two things at once is pretty obviously not gonna work. So that leads to this very important rule:

A component function must return a single element.

But wait! Could you return an array? It’s just JavaScript after all…

// This JSX:
function HelloWorld() {
  return [<Hello />, <World />];
}

// Would turn into this JS
// (notice the brackets).
function HelloWorld() {
  return [
    React.createElement(
      Hello,
      null
    ),
    React.createElement(World, null)
  ];
}

Try it out! It renders correctly.

But if you open up the browser console, you’ll see a warning:

Each child in an array or iterator should have a unique “key” prop.

As the warning suggests, React requires a unique key prop for each JSX element in an array. We’ll learn more about the key prop later on, but in the meantime, there are two ways to solve this problem: either wrap the elements in a single enclosing tag, or wrap them in a fragment.

Wrap With a Tag

The most obvious way to return multiple elements is to wrap them in an enclosing tag, like a <div>or <span>. However, it has the side effect of influencing the DOM structure.

For example, this React component…

function HelloWorld() {
  return (
    <div>
      <Hello/> <World/>!
    </div>
  );
}
…will render a DOM structure like this:

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

…will render a DOM structure like this:

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

A lot of the time, this is perfectly fine. But sometimes, you won’t want to have a wrapper element, like if you have a component that returns two table cells:

function NameCells() {
  return (
    <td>First Name</td>
    <td>Last Name</td>
  );
}

You can’t wrap these elements in a <div>, because the <td> table cells need to be direct descendants of a <tr> table row. How can you combine them?

Fragments

React’s answer is the fragment. This component was added in React 16.2, and can be used like this:

function NameCells() {
  return (
    <React.Fragment>
      <td>First Name</td>
      <td>Last Name</td>
    </React.Fragment>
  );
}

After rendering, the React.Fragment component will “disappear”, leaving only the children inside it, so that the DOM structure will have no wrapper components.

Fragments make it easier to produce valid HTML (such as keeping <td> elements directly inside <tr>s), and they keep the DOM structure flatter which makes it easier to write semantic HTML (which is also usually more accessible HTML).

Fragment Syntax

If you think React.Fragment looks clunky, I don’t blame you. JSX supports a special syntax that looks like an “empty tag” and is much nicer to write:

function NameCells() {
  return (
    <>
      <td>First Name</td>
      <td>Last Name</td>
    </>
  );
}

This <></> syntax is the preferred way to write fragments, and this feature will be available as long as you’re working in a new-enough project (Babel 7+, Create React App 2+)

JavaScript in JSX

You can insert real JavaScript expressions inside JSX code, and in fact, you’ll do this quite often. Surround JavaScript with single braces like this:

function SubmitButton() {
  const buttonLabel = "Submit";
  return (
    <button>{buttonLabel}</button>
  );
}

Remember that this will be compiled to JavaScript, which means that the JS inside the braces must be an expression. An expression produces a value. These are expressions:

1 + 2
buttonLabel
aFunctionCall()
aFunctionName

Each of these results in a single value. In contrast, statements do not produce values and can’t be used inside JSX. Here are some examples of statements:

const a = 5
if(true) { 17; }
while(i < 7) { i++ }

None of these things produces a value. const a = 5 declares a variable with the value 5, but it does not return that value.

Another way to think of statement vs expression is that expressions can be on the right hand of an assignment, but statements cannot.

// These aren't valid JS:
a = let b = 5;
a = if(true) { 17; }

You can also ask yourself, “Could I return this value from a function?” If the answer is yes, that’s an expression and you can write it inside JSX within {single braces}.