← React for Beginners

Components and JSX

Lesson 03 of 12 · 14 min

You already have one component. It is the whole of src/App.jsx:

function App() {
  return <h1>Hello from React</h1>;
}

export default App;

A function that returns markup. That is the entire definition. The two halves worth pulling apart are what makes a function a component, and what the markup can do.

Writing a second one

Add a function above App and use it inside:

function Greeting() {
  return <p>Nice to meet you.</p>;
}

function App() {
  return (
    <div>
      <h1>Hello from React</h1>
      <Greeting />
    </div>
  );
}

export default App;

Save, and the paragraph appears. Three things just happened.

<Greeting /> is a function call. Not a string, not a template - React sees that tag, recognises it as one of yours, calls Greeting(), and puts whatever it returns in that spot. Writing a component and using it are as ordinary as writing a function and calling it, because that is what they are.

The name has to start with a capital letter. This is a hard rule, not a convention. Lowercase tags are treated as HTML elements, so <greeting /> asks for an HTML element called “greeting”. You get an empty <greeting></greeting> in the page and none of your paragraph. <Greeting /> is capitalised, so React looks for a variable of that name instead.

React tells you exactly this. Open the browser console and it is there:

The tag <greeting> is unrecognized in this browser.
If you meant to render a React component, start its name with an uppercase letter.

When a component renders nothing, the console usually already knows why. Get in the habit of having it open. Most of this course’s dead ends are two seconds long if you do and twenty minutes long if you do not.

App returns one div wrapping both children. Remove it, return the h1 and the <Greeting /> side by side, and the build fails.

The rules of JSX

JSX is not HTML and it is not a string. It is syntax that the build tool turns into JavaScript function calls before the browser ever sees it - which is why you needed Vite in the first place. Because it becomes JavaScript, it inherits some constraints that HTML does not have.

One root element

A function returns one value, so a component returns one element. These two are the same rule:

// Fails. Two things, no container.
function App() {
  return (
    <h1>Hello</h1>
    <p>Goodbye</p>
  );
}
Adjacent JSX elements must be wrapped in an enclosing tag.

Wrapping them in a <div> fixes it, but that is a real div in the page, and adding one purely to satisfy a syntax rule litters your HTML. So JSX has an empty tag that groups children without rendering anything:

function App() {
  return (
    <>
      <h1>Hello</h1>
      <p>Goodbye</p>
    </>
  );
}

That is a fragment. Use it whenever the wrapper would exist only to be a wrapper. Use a real div when you actually want a div - to style it, for instance.

So the <div> in your own App was the wrong choice: it existed only to hold two children. Change it to <> and </> now.

Every tag closes

HTML forgives <br>, <img src="…"> and a <li> you never closed. JSX does not. Every element closes, and elements with no children close themselves:

<br />
<img src="/cat.jpg" alt="A cat" />
<input type="text" />

Forget the slash and you get another error that says the thing:

Expected corresponding JSX closing tag for 'br'.

className, not class

<p className="lede">Hello</p>

Because JSX becomes JavaScript, and class is a reserved word in JavaScript. The attribute is className, which is also what the DOM property has always been called. for on a label has the same problem and becomes htmlFor.

Both are worth knowing, because the failure is not obvious. React passes the attribute through to the DOM anyway - write class="lede" and your styling works - but it logs a warning while it does it:

Invalid DOM property `class`. Did you mean `className`?

So the code appears to work and the console disagrees. Believe the console.

Everything else is what you already know, with one twist: multi-word attributes are camel-cased. onclick becomes onClick, tabindex becomes tabIndex, maxlength becomes maxLength.

Braces let JavaScript back in

This is the part that makes JSX worth having. Anywhere inside JSX, { and } mean “stop writing markup, evaluate this JavaScript expression, and put the result here”:

function App() {
  const name = "Gjorge";
  const year = new Date().getFullYear();

  return (
    <>
      <h1>Hello, {name}</h1>
      <p>It is {year}.</p>
      <p>Next year is {year + 1}.</p>
    </>
  );
}

Anything that produces a value works: a variable, arithmetic, a function call, a property lookup, a ternary. What does not work is a statement - if, for and while are not expressions and cannot go inside braces. That sounds limiting. It is not, for reasons lesson 5 and lesson 6 are about.

Braces work in attributes too, and this is where the difference between a string and an expression starts to matter:

function Photos() {
  const imageUrl = "/tabby.jpg";

  return (
    <figure>
      {/* quotes give the literal string "/cat.jpg" */}
      <img src="/cat.jpg" alt="A cat" />

      {/* braces give whatever the variable holds */}
      <img src={imageUrl} alt="Another cat" />

      {/* the number 40, not the string "40" */}
      <input maxLength={40} />
    </figure>
  );
}

Quotes for a literal string, braces for everything else.

Those {/* … */} are how you comment inside JSX. Markup is not JavaScript, so a comment has to be smuggled in as an expression like anything else. A bare // between tags ends up on the page as text.

Two braces means an object

One case trips up nearly everyone. Inline styles take an object, not a string:

<p style={{ color: "crimson", fontSize: 20 }}>Careful.</p>

That is not special syntax. The outer braces are “here comes an expression”, the inner ones are an object literal, and the two just happen to sit next to each other. Note the camel case again - fontSize, not font-size, because those are JavaScript property names now. A number with no unit is treated as pixels.

You will not use this much. Stylesheets are still the better answer for almost everything, and className is how you reach them. But the double brace looks like a typo the first time you meet it, and now it does not.

Splitting a page into components

Any piece of JSX can be lifted out into its own function and used by name:

function SiteHeader() {
  return (
    <header>
      <h1>My first React app</h1>
      <p>Learning the basics.</p>
    </header>
  );
}

function Footer() {
  return <footer>Built in {new Date().getFullYear()}.</footer>;
}

function App() {
  return (
    <>
      <SiteHeader />
      <main>
        <p>The interesting part goes here.</p>
      </main>
      <Footer />
    </>
  );
}

export default App;

App now reads as an outline of the page. That is the point of components, and it is the same instinct that makes you break a long function into shorter ones: not because the computer cares, but because a reader does.

When you split a component into its own file, it is the import and export from the last lesson and nothing more. Footer.jsx ends with export default Footer, and App.jsx starts with import Footer from "./Footer.jsx". One component per file is the usual convention, and there is no rule enforcing it.

The limit you are about to hit

SiteHeader above is stuck. It renders exactly one heading and exactly one paragraph, so the moment you want a second header with different words you are copying the function and editing the copy - which is where the whole idea falls apart.

What is missing is a way to hand a component some data when you use it, the way you hand a function arguments. That is the next lesson.

Recap

A component is a function that returns markup, and using one is calling it. Its name must be capitalised or React will look for an HTML element instead and quietly render nothing. JSX is markup that compiles to JavaScript, which is why it needs one root element (or a <> fragment), closes every tag, spells it className, and lets any JavaScript expression in through braces.

Next: props - passing data into a component, so the same component can render different things.