Introduction to JSX page
Learn how to use JSX to define your UI in React.
Overview
In this section, we will:
- Learn the basics of JSX
- Discover the differences between JSX and HTML
- Use JavaScript variables and expressions in JSX
- Work with conditionals and loops in JSX
Objective 1: Create a UI with JSX
Now that we have our project set up, let’s update our page to look like the design below:
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. React transforms that JSX into the HTML the browser displays. JSX can be written alongside standard JavaScript, which makes for a powerful programming environment.
Basic JSX looks like HTML
const greeting = <h1>Hello, world!</h1>
This code snippet creates a simple JSX element: an <h1>
header with the text “Hello, world!” This is similar to writing HTML, but it’s actually JSX inside a JavaScript file.
Embedding a JavaScript expression in JSX
const name = "Alice"
const greeting = <h1>Hello, {name}!</h1>
Here, we embed a JavaScript expression {name}
within JSX. The value of the name variable is displayed within the <h1>
tag.
Combining JSX with standard JavaScript
function welcome(name) {
return <h1>Hello, {name}</h1>
}
const welcomeMessage = welcome("Alice")
This example illustrates a regular JavaScript function being called with a single argument. The welcome
function returns JSX, showing how JSX can be seamlessly integrated within standard JavaScript functions.
JSX transpiled
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.
function welcome(name) {
return React.createElement("h1", null, `Hello, ${name}!`)
}
const welcomeMessage = welcome("Alice")
This transformation is handled by tools like Vite during the build process, allowing us to write more readable and maintainable code using JSX.
Differences between JSX and HTML
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:
HTML has attributes, JSX has props
This nomenclature difference is because they are technically different, though they might appear to be the same visually.
When talking about the HTML below, you might say that the href
attribute is a URL:
<a href="https://www.bitovi.com/academy/">Bitovi Academy</a>
The above is valid JSX too, but we would say that the href
prop is being passed into the anchor element.
Tags must close
In HTML, some elements are self-closing and don’t need a closing tag.
For example: the img
element is a self-closing element:
<img alt="" src="image.png" />
In JSX, no elements are self-closing, which means that all elements must have a closing tag, like the img
below:
<img alt="" src="image.png" />
Writing comments
In HTML, comments are written using the <!-- -->
syntax, seen below. Anything inside these comment tags is ignored by the browser and is not rendered or executed.
<p>
<!-- This is an HTML comment -->
Visible content
</p>
In JSX, comments follow the JavaScript comment syntax. Since JSX is transpiled into JavaScript, you must use JavaScript’s {/* */} syntax for comments within the JSX part of your code.
const content = (
<p>
{/* This is a JSX comment. */}
Visible content
</p>
)
Reserved words are renamed
The HTML attributes class
and for
are reserved words in JavaScript. In JSX, these are renamed to className
and htmlFor
, respectively.
const content = (
<p className="form-field">
<label htmlFor="name-input">Name:</label>
<input id="name-input" />
</p>
)
Style prop
In HTML, the appearance of most elements can be altered using the style
attribute. React supports a style
prop, but it accepts an object, not a string. The style object has properties whose names are camel-case versions of their CSS counterparts. IE: "font-style" becomes fontStyle
.
const content = <p style={{ fontStyle: "italic" }}>Restaurants</p>
As we go through this training, you’ll learn additional differences.
Convention: Parenthesis
When dealing with JSX that needs multiple lines, the convention is to wrap it in parethesis. This helps keep your JSX clean and clear, rather than mixing it with the javascript around it.
function Form() {
return (
<p className="form-field">
<label htmlFor="name-input">Name:</label>
<input id="name-input" />
</p>
)
}
Convention: Implicit Returns
When creating functions that have no logic and just return JSX, especially when they're an argument to a function, the convention is to use an arrow function with an implicit return. This is nearly always coupled with the parenthesis convention, too. (Don’t worry: you’ll learn about .map
in the next objective.)
const data = ["one", "two"]
function List() {
return (
<div>
{data.map((name) => (
<li key={name}>{name}</li>
))}
</div>
)
}
Setup 1
We’ve created an assets package with images and styles you’ll need to build the application. Install the place-my-order-assets
package.
✏️ Run:
npm install place-my-order-assets@0
✏️ Update src/index.css to be:
@import "place-my-order-assets/css/place-my-order-assets.css";
Verify 1
✏️ 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 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 1
Take the below HTML and convert it to JSX in app.tsx
:
<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 1
Click to see the solution
✏️ Update src/App.tsx to be:
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: Expressions and loops in JSX
Next, we want to render a list of restaurants in our application:
To do so, we‘ll learn about:
- Using JavaScript variables and expressions in JSX
- Working with conditionals and loops in JSX
Using JavaScript variables and expressions in JSX
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"
const content = <p>Welcome to {name}!</p>
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",
}
const content = (
<main>
<h1>Hi I’m {person.name.toUpperCase()}!</h1>
<p>I’m a {person.profession} living in Philadelphia.</p>
</main>
)
JSX is a syntax extension for JavaScript
Remember, JSX is an alternative syntax for normal JavaScript—it is not magic. This means that you can use JSX as a normal value, too.
const header = <h1>Hello World</h1>
const body = <p>My name is {"Mike"}</p>
function MyPage() {
return (
<main>
{header}
{body}
</main>
)
}
If rendered, page
will output:
<main>
<h1>Hello World</h1>
<p>My name is Mike</p>
</main>
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, `My name is ${"Mike"}`)
const page = React.createElement("main", null, [header, body])
Working with conditionals and loops in JSX
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 conditionals
Conditions can be re-written using the ternary operator.
// This does not work
const content = (
<p>
{
if (a === b) { // Control flow does not belong in JSX
"a and b are equal"
} else {
"a and b are different"
}
}
</p>
)
// But the same can be accomplished with ternaries
const content = (
<p>
{a === b // Ternaries are expressions.
? "a and b are equal"
: "a and b are different"}
</p>
)
If ternaries seem excessive for any particular case, you can write all your logic in a separate function and invoke it from within JSX.
function makeResult() {
return a === b ? "a and b are equal" : "a and b are different"
}
const content = <p>{makeResult()}</p>
Using loops
JSX does not support traditional loop statements like for
, while
, or do...while
directly within JSX.
The example below will not work:
const names = ['Alfa', 'Bravo', 'Charlie'];
// This does not work
const content = (
<ul>
{
// Control flow does not belong in JSX
for (let name of names) {
<li>name</li>
}
}
</ul>
)
The Array.map()
function is the most common and idiomatic way to render lists in JSX.
It’s especially useful for rendering arrays of data as HTML elements.
const names = ["Alfa", "Bravo", "Charlie"]
// This will work
const content = (
<ul>
{names.map((name) => {
return <li key={name}>{name}</li>
})}
</ul>
)
That will produce the following HTML:
<ul>
<li>Alfa</li>
<li>Bravo</li>
<li>Charlie</li>
</ul>
There are lots of ways to iterate over arrays in JavaScript with functions like Array.map
, Array.filter
, and Array.reduce
. These all work in JSX!
The key
prop
Did you notice the key
prop in the example above?
When rendering a list of elements, React needs a way to uniquely identify each element. This helps React understand which items have changed, been added, or removed, which is crucial for efficient re-rendering.
Each key should be a unique identifier among siblings. Keys should be stable (not change over time), predictable (generated in a predictable manner), and unique (no two elements in the list should have the same key).
It’s often convenient to use IDs from your data as keys. For example, if our data had id
properties for each name, then we could use those as the key
prop, even if there were duplicate names in the array:
const names = [
{ id: "550e8400", name: "Alfa" },
{ id: "f47ac10b", name: "Bravo" },
{ id: "5a3c9dd9", name: "Alfa" },
{ id: "3d3f6f4d", name: "Charlie" },
{ id: "aab3fcba", name: "Delta" },
]
const content = (
<ul>
{names.map(({ id, name }) => {
return <li key={id}>{name}</li>
})}
</ul>
)
That will produce the following HTML:
<ul>
<li>Alfa</li>
<li>Bravo</li>
<li>Alfa</li>
<li>Charlie</li>
<li>Delta</li>
</ul>
🔦 During development, If you forget to provide a key
prop for items in an array React will log the following error to the browser’s console:
Setup 2
✏️ 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 2
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.
Exercise 2
- Update the existing JSX to render the list of restaurants
- Use
Array.map()
to iterate over therestaurants.data
- Make sure to use
key
inside the.map()
- Render
<p>No restaurants.</p>
if, hypothetically, there weren’t any restaurants.
Solution 2
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.