Introduction to JSX page

Learn how to use JSX to define your UI in React.

Overview

In this section, we will:

  • Learn to use JSX to define our UI
  • Install project assets
  • Create our first component
  • Add variables to JSX
  • Work with loops in JSX

Objective 1: Creating a UI with JSX

Key concepts

What is JSX?

JSX is used by React developers to define the user interface.

JSX is a special syntax designed to look almost identical to HTML. Developers define the UI using JSX and React transforms it into the HTML the browser displays. JSX can be written alongside standard JavaScript, which makes for a powerful programming environment.

React has a procedural React.createElement syntax, but most applications do not use it directly. Instead, views are defined and maintained in JSX and will automatically be transpiled into the equivalent React.createElement calls at build-time.

HTML differences

JSX looks a lot like HTML, and that's intentional. Browsers understand HTML, JavaScript, and CSS. Eventually, anything we build has to be converted into one of those 3 syntaxes. Since our UI code will eventually end up as HTML, using JSX means it will be easier to understand and debug the end result in the browser.

However, JSX is converted into JavaScript and therefore is not an exact mirror of HTML. Some of the most noticeable differences include:

Say "props" instead of "attributes." This nomenclature difference is because they are technically different, though visually they appear the same. When the following image tag appears in HTML, the "src" is called an attribute. In JSX, the "src" is a prop or property.

<img src="image.png">

Reserved words are renamed. The HTML attributes "class" and "for" are reserved words in JavaScript. These had to be renamed to "className" and "htmlFor" respectively.

<label className="label" htmlFor="name-input">Name:</input>
<input className="input" id="name-input" />

As we go through this training, you'll learn additional differences.

Exercise 1

Acceptance criteria

  • Create the homepage using JSX
  • Match the design below

mockup

Setup

We've created an assets package with images and CSS you'll need to build the application. Install the place-my-order-assets package.

✏️ Run:

npm install place-my-order-assets

✏️ Remove the contents of the src/App.css file so it’s completely blank.

✏️ Update src/index.css to be:

@import "place-my-order-assets/css/place-my-order-assets.css";

Verify

You should have already installed Vitest in the previous section. If you haven't done so already, create a test file at src/App.test.tsx and copy the following tests into your test file.

✏️ Copy:

import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import { describe, expect, it } from 'vitest';

import App from './App';

describe('App component', () => {
  it('renders the Restaurants header', () => {
    render(<App />);
    expect(screen.getByText(/Restaurants/i)).toBeInTheDocument();
  });

  it('renders the restaurant image', () => {
    render(<App />);
    const img = screen.getByRole('img');
    expect(img).toHaveAttribute('src', '/node_modules/place-my-order-assets/images/4-thumbnail.jpg');
    expect(img).toHaveAttribute('width', '100');
    expect(img).toHaveAttribute('height', '100');
  });

  it('renders the address', () => {
    render(<App />);
    const addressDiv = screen.getByText(/230 W Kinzie Street/i).closest('div');
    expect(addressDiv).toHaveTextContent('230 W Kinzie Street');
    expect(addressDiv).toHaveTextContent('Green Bay, WI 53205');
  });

  it('renders the hours and price information', () => {
    render(<App />);
    const hoursPriceDiv = screen.getByText(/\$\$\$/i).closest('div');
    expect(hoursPriceDiv).toHaveTextContent('$$$');
    expect(hoursPriceDiv).toHaveTextContent('Hours: M-F 10am-11pm');
  });

  it('indicates if the restaurant is open now', () => {
    render(<App />);
    expect(screen.getByText('Open Now')).toBeInTheDocument();
  });

  it('renders the details button with correct link', () => {
    render(<App />);
    const detailsButton = screen.getByRole('link');
    expect(detailsButton).toHaveAttribute('href', '/restaurants/poutine-palace');
    expect(screen.getByText('Details')).toBeInTheDocument();
  });
});

✏️ Run:

npm run test

Note that we won't be able to write an automated test that verifies the styles and images appear as desired, so you will have to test those manually.

Exercise

Take the below HTML and convert it to JSX:

<div class="restaurants">
    <h2 class="page-header">Restaurants</h2>
    <div class="restaurant">
        <img alt="" src="/node_modules/place-my-order-assets/images/4-thumbnail.jpg" width="100" height="100">
        <h3><!-- NAME GOES HERE --></h3>
        <div class="address">
            230 W Kinzie Street
            <br>
            Green Bay, WI 53205
        </div>
        <div class="hours-price">
            $$$
            <br>
            Hours: M-F 10am-11pm
            <span class="open-now">
                Open Now
            </span>
        </div>
        <a class="btn" href="/restaurants/poutine-palace">
            Details
        </a>
        <br>
    </div>
</div>

Having issues with your local setup? You can use either StackBlitz or CodeSandbox to do this exercise in an online code editor.

Solution

Click to see the solution

✏️ Update src/App.tsx

import './App.css'

function App() {
  return (
    <>
      <div className="restaurants">
        <h2 className="page-header">Restaurants</h2>
        <div className="restaurant">
          <img alt="" src="/node_modules/place-my-order-assets/images/4-thumbnail.jpg" width="100" height="100" />
          <h3>{/* NAME GOES HERE */}</h3>
          <div className="address">
            230 W Kinzie Street
            <br />
            Green Bay, WI 53205
          </div>
          <div className="hours-price">
            $$$
            <br />
            Hours: M-F 10am-11pm
            <span className="open-now">
              Open Now
            </span>
          </div>
          <a className="btn" href="/restaurants/poutine-palace">
            Details
          </a>
          <br />
        </div>
      </div>
    </>
  )
}

export default App

Having issues with your local setup? See the solution in StackBlitz or CodeSandbox.

Objective 2: Loops in JSX

Key concepts

  • JSX Interpolation
  • Loops in JSX

JSX Interpolation

JSX is dynamic. You can insert values from variables and objects into your JSX as we did with the image URL in the previous section.

const name = 'Bitovi';

<div className="button primary">Welcome to {name}!</div>;

In the code above, use the {name} syntax to tell JSX that to render the value stored in the name variable (i.e. "Bitovi") into our view.

You can take this a step further by interpolating multiple values, and using JavaScript functions to transform data on the fly. Anything that goes inside {} is executed as normal JavaScript. These are the same rules as the brackets on a prop: any JavaScript expression is valid inside the curly brackets.

const person = {
  name: 'mike',
  profession: 'programmer',
};

<div className="button primary">
  <h1>Hi I'm {person.name.toUpperCase()}!</h1>
  <p>I'm a {person.profession} living in Philadelphia</p>
</div>;

JSX is JavaScript

Remember, JSX is simply an alternative syntax for normal JavaScript—it is not magic. This means that you can use JSX as a normal value.

const header = <h1>Hello World</h1>;
const body = <p>My name is {'Mike'}</p>;

function MyPage() {
  return (
    <div>
      {header}
      {body}
    </div>
  );
}

ReactDOM.render(<MyPage />, document.getElementById('root'));

If rendered, page will output:

<div>
  <h1>Hello World</h1>
  <p>My name is Mike</p>
</div>

If this surprises you, remember that underneath the syntactic sugar, JSX is nothing more than React.createElement calls:

const header = React.createElement('h1', null, 'Hello World');
const body = React.createElement('p', null, `Hello ${'Mike'}`);

const page = React.createElement('div', null, [header, body]);

Conditions and loops

Only expressions which return a value may be interpolated. This includes static values, variables and calls to functions. It does not include control-flow statements such as if, case, for, while. These can either be abstracted behind a function, which is then called within the JSX or be re-written in a JSX-friendly way.

To put it simply: only things that you could pass into a function can be used inside the brackets.

Using Conditions

Conditions can be re-written using the ternary operator.

// This does not work
<div>
  {
    if (a === b) { // Control flow does not belong in JSX
      "a and b are equal"
    } else {
      "a and b are different"
    }
  }
</div>

// But the same can be accomplished with ternaries
<div>
  {a === b // Ternaries are expressions. They return a value.
    ? 'a and b are equal'
    : 'a and b are different'}
</div>

If ternaries seem excessive for any particular case, you can write all your logic in a separate function and invoke it from within JSX.

<div>{outputResult()}</div>

Using Loops

// This does not work
<div>
  {
    for (let n of [1, 2, 3, 4]) { // Control flow does not belong in JSX
      <span>n</span>
    }
  }
</div>

If you want to iterate within JSX, use methods such as Array.map, Array.filter and Array.reduce:

✏️ Note: Due to how React stores elements in memory, list items require a stable key to identify them in the Virtual DOM. You can learn more in the React docs

// Mapping values to JSX elements
<div>
  {[1, 2, 3, 4].map((n, index) => (
    <span key={index}>{n}</span>
  ))}
</div>

Mapping over the array above will render as the following HTML.

<div>
  <span>1</span>
  <span>2</span>
  <span>3</span>
  <span>4</span>
</div>

Exercise 2

Acceptance Criteria

  • Set the H1 tag to "Ordering food has never been easier"
  • Display a list of restaurants: 'Cheese Curd City' & 'Poutine Palace'

Requirements

  • Use the proper ul and li tags to create the list
  • List items should have a unique key

Setup

✏️ Update src/App.tsx to be:

import CheeseThumbnail from 'place-my-order-assets/images/2-thumbnail.jpg'
import PoutineThumbnail from 'place-my-order-assets/images/4-thumbnail.jpg'
import './App.css'

function App() {
  const restaurants = {
    data: [
      {
        name: 'Cheese Curd City',
        slug: 'cheese-curd-city',
        images: {
          thumbnail: CheeseThumbnail,
        },
        address: {
          street: '2451 W Washburne Ave',
          city: 'Green Bay',
          state: 'WI',
          zip: '53295',
        },
        _id: 'Ar0qBJHxM3ecOhcr',
      },
      {
        name: 'Poutine Palace',
        slug: 'poutine-palace',
        images: {
          thumbnail: PoutineThumbnail,
        },
        address: {
          street: '230 W Kinzie Street',
          city: 'Green Bay',
          state: 'WI',
          zip: '53205',
        },
        _id: '3ZOZyTY1LH26LnVw',
      },
    ]
  };

  return (
    <>
      <div className="restaurants">
        <h2 className="page-header">Restaurants</h2>
        <div className="restaurant">
          <img alt="" src="/node_modules/place-my-order-assets/images/4-thumbnail.jpg" width="100" height="100" />
          <h3>{/* NAME GOES HERE */}</h3>
          <div className="address">
            230 W Kinzie Street
            <br />
            Green Bay, WI 53205
          </div>
          <div className="hours-price">
            $$$
            <br />
            Hours: M-F 10am-11pm
            <span className="open-now">
              Open Now
            </span>
          </div>
          <a className="btn" href="/restaurants/poutine-palace">
            Details
          </a>
          <br />
        </div>
      </div>
    </>
  )
}

export default App

Verify

Add these tests into your application to verify you have met the acceptance criteria. Note how React Testing Library allows our tests to specify exact elements.

✏️ Update src/App.test.tsx to be:

import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import { describe, expect, it } from 'vitest';

import App from './App';

describe('App component', () => {
  it('renders the Restaurants header', () => {
    render(<App />);
    expect(screen.getByText(/Restaurants/i)).toBeInTheDocument();
  });

  it('renders the restaurant images', () => {
    render(<App />);
    const images = screen.getAllByRole('img');
    expect(images[0]).toHaveAttribute('src', expect.stringContaining('2-thumbnail.jpg'));
    expect(images[0]).toHaveAttribute('width', '100');
    expect(images[0]).toHaveAttribute('height', '100');
    expect(images[1]).toHaveAttribute('src', expect.stringContaining('4-thumbnail.jpg'));
    expect(images[1]).toHaveAttribute('width', '100');
    expect(images[1]).toHaveAttribute('height', '100');
  });

  it('renders the addresses', () => {
    render(<App />);
    const addressDivs = screen.getAllByText(/Washburne Ave|Kinzie Street/i);
    expect(addressDivs[0]).toHaveTextContent('2451 W Washburne Ave');
    expect(addressDivs[0]).toHaveTextContent('Green Bay, WI 53295');
    expect(addressDivs[1]).toHaveTextContent('230 W Kinzie Street');
    expect(addressDivs[1]).toHaveTextContent('Green Bay, WI 53205');
  });

  it('renders the hours and price information for each restaurant', () => {
    render(<App />);
    const hoursPriceDivs = screen.getAllByText(/\$\$\$/i);
    hoursPriceDivs.forEach(div => {
      expect(div).toHaveTextContent('$$$');
      expect(div).toHaveTextContent('Hours: M-F 10am-11pm');
    });
  });

  it('indicates if the restaurant is open now for each restaurant', () => {
    render(<App />);
    const openNowTags = screen.getAllByText('Open Now');
    expect(openNowTags.length).toBeGreaterThan(0);
  });

  it('renders the details buttons with correct links for each restaurant', () => {
    render(<App />);
    const detailsButtons = screen.getAllByRole('link');
    expect(detailsButtons[0]).toHaveAttribute('href', '/restaurants/cheese-curd-city');
    expect(detailsButtons[1]).toHaveAttribute('href', '/restaurants/poutine-palace');
    detailsButtons.forEach(button => {
      expect(button).toHaveTextContent('Details');
    });
  });
});

Having issues with your local setup? You can use either StackBlitz or CodeSandbox to do this exercise in an online code editor.

Solution

Click to see the solution

✏️ Update src/App.tsx

import CheeseThumbnail from 'place-my-order-assets/images/2-thumbnail.jpg'
import PoutineThumbnail from 'place-my-order-assets/images/4-thumbnail.jpg'
import './App.css'

function App() {
  const restaurants = {
    data: [
      {
        name: 'Cheese Curd City',
        slug: 'cheese-curd-city',
        images: {
          thumbnail: CheeseThumbnail,
        },
        address: {
          street: '2451 W Washburne Ave',
          city: 'Green Bay',
          state: 'WI',
          zip: '53295',
        },
        _id: 'Ar0qBJHxM3ecOhcr',
      },
      {
        name: 'Poutine Palace',
        slug: 'poutine-palace',
        images: {
          thumbnail: PoutineThumbnail,
        },
        address: {
          street: '230 W Kinzie Street',
          city: 'Green Bay',
          state: 'WI',
          zip: '53205',
        },
        _id: '3ZOZyTY1LH26LnVw',
      },
    ]
  };

  return (
    <>
      <div className="restaurants">
        <h2 className="page-header">Restaurants</h2>
        {restaurants.data ? (
          restaurants.data.map(({ _id, address, images, name, slug }) => (
            <div key={_id} className="restaurant">
              <img src={images.thumbnail} alt="" width="100" height="100" />
              <h3>{name}</h3>

              {address && (
                <div className="address">
                  {address.street}
                  <br />
                  {address.city}, {address.state} {address.zip}
                </div>
              )}

              <div className="hours-price">
                $$$
                <br />
                Hours: M-F 10am-11pm
                <span className="open-now">Open Now</span>
              </div>

              <a className="btn" href={`/restaurants/${slug}`}>
                Details
              </a>
              <br />
            </div>
          ))
        ) : (
          <p>No restaurants.</p>
        )}
      </div>
    </>
  )
}

export default App

Having issues with your local setup? See the solution in StackBlitz or CodeSandbox.

Next steps

Next we will learn about building out a React application with components.