Introduction to JSX page

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

Overview

In this section, you will:

  • Embed JavaScript expressions in JSX.
  • Combine JSX with standard JavaScript
  • Use JavaScript variables and expressions in JSX.
  • Write 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:

Screenshot of a mobile application interface text “Place My Order: Coming Soon!” and “Illinois“.

What is JSX?

In React Native development, JSX is utilized to define the user interface of mobile applications.

JSX stands for JavaScript XML, allowing you to write UI components that look similar to XML or HTML in their structure but work within the JavaScript environment. React Native transforms JSX into native components that are displayed on the mobile device. JSX can be combined seamlessly with JavaScript, which makes for a powerful programming environment.

Basic JSX looks like XML

const greeting = <Text>Hello, world!</Text>

In this example, a <Text> component is used to render the text “Hello, world!”

While this appears similar to XML or HTML, it is JSX used in a React Native project within a JavaScript file.

Embedding a JavaScript expression in JSX

const name = "Alice"
const greeting = <Text>Hello, {name}!</Text>

Here, we embed a JavaScript expression {name} within JSX. The value of the name variable is displayed within the <Text> component.

Combining JSX with standard JavaScript

function welcome(name) {
  return <Text>Hello, {name}</Text>
}

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 Native 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("Text", null, `Hello, ${name}!`)
}

const welcomeMessage = welcome("Alice")

This transformation is handled by tools like Metro during the build process, allowing us to write more readable and maintainable code using JSX.

Differences between JSX and XML

While JSX syntactically resembles XML or HTML, there are several key differences in how they are used and processed. Understanding these differences is crucial for developers transitioning from XML’s strict standards or HTML’s looser structure.

Here are some of the most noticeable differences between JSX and XML:

XML has attributes, JSX has props

In XML, attributes are used to provide additional information about elements, and they are always treated as strings. This makes XML straightforward but less flexible in terms of integrating dynamic content directly within the markup.

For example, consider an XML element representing a user:

<user age="37" isActive="true" name="Jane Doe"></user>

JSX allows props to be much more than just strings. Props in JSX can be any JavaScript expression, including any other JavaScript primitive, object, function, etc.

const userElement = <User age={37} isActive name="Mary Jackson" />

In the code above, the age, isActive, and name props are all passed into the User component.

Writing comments

In XML, comments are written using the <!-- --> syntax, seen below. Anything inside these comment components is ignored by the parser and is not rendered or executed.

<p>
  <!-- This is an XML 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 = (
  <Text>
    {/* This is a JSX comment. */}
    Visible content
  </Text>
)

Style prop

If you’re familiar with front-end web development, you might know that the appearance of most HTML elements can be altered using the style attribute.

React Native 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, e.g. font-style becomes fontStyle.

const content = <Text style={{ fontStyle: "italic" }}>Restaurants</Text>

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

Using View

The View component is one of the most fundamental building blocks in React Native. It is a container component that can hold one or more other components while grouping them together for accessibility, styling, touch handling, and more.

If you’re familiar with web development, a <View> is similar to a <div>.

import { Text, View } from "react-native"

const App: React.FC = () => {
  return (
    <View>
      <Text>Hello, React Native!</Text>
    </View>
  )
}

export default App

Using ScrollView

The ScrollView component is a generic scrolling container that can host multiple components and views. It is useful when the content you need to display exceeds the screen size, enabling vertical or horizontal scrolling.

import { ScrollView, Text } from "react-native"

const App: React.FC = () => {
  return (
    <ScrollView>
      <Text>Ada Lovelace</Text>
      <Text>Grace Hopper</Text>
      <Text>Katherine Johnson</Text>
      <Text>Margaret Hamilton</Text>
      <Text>Radia Perlman</Text>
      <Text>Hedy Lamarr</Text>
    </ScrollView>
  )
}

export default App

Using SafeAreaView

The SafeAreaView component ensures that the content is rendered within the safe area boundaries of a device. This is particularly important for devices with notches, home indicators, or rounded corners.

import { SafeAreaView, Text } from "react-native"

const App: React.FC = () => {
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <Text>Content within safe area.</Text>
    </SafeAreaView>
  )
}

export default App

You’ll notice the flex: 1 style that we’ve added to the component. We will get into styles more later, but for now, know that flex: 1 will make the component take up all the available space.

In general, you always want to have your content wrapped in a <SafeAreaView> so the device’s hardware boundaries don’t overlap with your app’s content.

Parenthesis (convention)

When dealing with JSX that needs multiple lines, the convention is to wrap it in parentheses. This helps keep your JSX clean and clear, rather than mixing it with the Javascript around it.

const Form: React.FC = () => {
  return (
    <View>
      <Text>Name:</Text>
      <TextInput value="Molly Holzschlag" />
    </View>
  )
}

Implicit returns (convention)

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"]

const List: React.FC = () => {
  return (
    <View>
      {data.map((name) => (
        <View key={name}>{name}</View>
      ))}
    </View>
  )
}

Setup 1

✏️ Update App.tsx to be:

import { SafeAreaView, ScrollView, Text, View } from "react-native"

const App: React.FC = () => {
  // Exercise: Use `SafeAreaView`, `ScrollView`, and `View` to wrap the existing `Text` component.
  return <Text>Place My Order: Coming Soon!</Text>
  // Exercise: Add a new `View` component to wrap `Text` that says “Illinois”.
}

export default App

Verify 1

✏️ Update App.test.tsx to be:

import { render, screen } from "@testing-library/react-native"

import App from "./App"

describe("App", () => {
  it("renders", async () => {
    render(<App />)
    expect(screen.getByText(/Place my order/i)).toBeOnTheScreen()
  })

  it("renders a state", async () => {
    render(<App />)
    expect(screen.getByText(/Illinois/i)).toBeOnTheScreen()
  })
})

Exercise 1

Update App.tsx to include the following:

  • Use SafeAreaView, ScrollView, and View to wrap the existing Text component.
  • Add a new View component to wrap Text that says “Illinois”.

Solution 1

If you’ve implemented the solution correctly, the tests will pass when you run npm run test!

Click to see the solution

✏️ Update App.tsx to be:

import { SafeAreaView, ScrollView, Text, View } from "react-native"

const App: React.FC = () => {
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <ScrollView>
        <View>
          <Text>Place My Order: Coming Soon!</Text>
        </View>
        <View>
          <Text>Illinois</Text>
        </View>
      </ScrollView>
    </SafeAreaView>
  )
}

export default App

Objective 2: Expressions and loops in JSX

Next, we want to render a list of state names in our application:

Screenshot of a mobile application interface text “Place My Order: Coming Soon!”, “Illinois“, and “Wisconsin“.

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 state name in the previous section.

const name = "Bitovi"

const content = <Text>Welcome to {name}!</Text>

In the code above, use the {name} syntax to tell JSX 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 = (
  <View>
    <Text>Hi I’m {person.name.toUpperCase()}!</Text>
    <Text>I’m a {person.profession} living in Philadelphia.</Text>
  </View>
)

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 = <Text>Hello World</Text>
const body = <Text>My name is {"Mike"}</Text>

const MyPage: React.FC = () => {
  return (
    <View>
      {header}
      {body}
    </View>
  )
}

If rendered, the screen will output:

<View>
  <Text>Hello World</Text>
  <Text>My name is Mike</Text>
</View>

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

const header = React.createElement("Text", null, "Hello World")
const body = React.createElement("Text", null, `My name is ${"Mike"}`)

const page = React.createElement("View", null, [header, body])

Working with conditionals and loops in JSX

Only expressions that 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 rewritten using the ternary operator.

The example below will not work:

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

But the same can be accomplished with ternaries in the following example:

const content = (
  <Text>
    {a === b // Ternaries are expressions.
      ? "a and b are equal"
      : "a and b are different"}
  </Text>
)

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 = <Text>{makeResult()}</Text>

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'];

const content = (
  <View>
    {
      // Control flow does not belong in JSX
      for (let name of names) {
        <Text>name</Text>
      }
    }
  </View>
)

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 components.

const names = ["Alfa", "Bravo", "Charlie"]

const content = (
  <View>
    {names.map((name) => {
      return <Text key={name}>{name}</Text>
    })}
  </View>
)

That will produce the following React Native views:

<View>
  <Text>Alfa</Text>
  <Text>Bravo</Text>
  <Text>Charlie</Text>
</View>

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 Native needs a way to uniquely identify each element. This helps React Native 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 = (
  <View>
    {names.map(({ id, name }) => {
      return <Text key={id}>{name}</Text>
    })}
  </View>
)

That will produce the following React Native views:

<View>
  <Text>Alfa</Text>
  <Text>Bravo</Text>
  <Text>Alfa</Text>
  <Text>Charlie</Text>
  <Text>Delta</Text>
</View>

During development, if you forget to provide a key prop for items in an array, React Native will log the following error to the console:

ERROR Warning: Each child in a list should have a unique "key" prop.

Check the render method of 'App'. See https://reactjs.org/link/warning-keys for more information.
  at Text (http://10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=com.solution&modulesOnly=false&runModule=true:66390:27)
  in App
  in RCTView (created by View)
  in View (created by AppContainer)
  in RCTView (created by View) in View (created by AppContainer)
  in AppContainer in solution

Setup 2

✏️ Update App.tsx to be:

import { SafeAreaView, ScrollView, Text, View } from "react-native"

const states = [
  {
    name: "Illinois",
    short: "IL",
  },
  {
    name: "Wisconsin",
    short: "WI",
  },
]

const App: React.FC = () => {
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <ScrollView>
        <View>
          <Text>Place My Order: Coming Soon!</Text>
        </View>
        <View>
          {/* Exercise: Update the existing JSX to render the list of state names.
          Use `Array.map()` to iterate over the `states`.
          Make sure to use `key` inside the `.map()`. */}
          <Text>Illinois</Text>
          {/* Exercise: Render `<Text>No states found</Text>` if, hypothetically, there weren’t any states. */}
        </View>
      </ScrollView>
    </SafeAreaView>
  )
}

export default App

Verify 2

✏️ Update App.test.tsx to be:

import { render, screen } from "@testing-library/react-native"

import App from "./App"

describe("App", () => {
  it("renders", async () => {
    render(<App />)
    expect(screen.getByText(/Place my order/i)).toBeOnTheScreen()
  })

  it("renders states", async () => {
    render(<App />)
    expect(screen.getByText(/Illinois/i)).toBeOnTheScreen()
    expect(screen.getByText(/Wisconsin/i)).toBeOnTheScreen()
  })
})

Exercise 2

  • Update the existing JSX to render the list of state names.
  • Use Array.map() to iterate over the states.
  • Make sure to use key inside the .map().
  • Render <Text>No states found</Text> if, hypothetically, there weren’t any states.

Solution 2

If you’ve implemented the solution correctly, the tests will pass when you run npm run test!

Click to see the solution

✏️ Update App.tsx to be:

import { SafeAreaView, ScrollView, Text, View } from "react-native"

const states = [
  {
    name: "Illinois",
    short: "IL",
  },
  {
    name: "Wisconsin",
    short: "WI",
  },
]

const App: React.FC = () => {
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <ScrollView>
        <View>
          <Text>Place My Order: Coming Soon!</Text>
        </View>
        <View>
          {states?.length ? (
            states.map((state) => <Text key={state.short}>{state.name}</Text>)
          ) : (
            <Text>No states found</Text>
          )}
        </View>
      </ScrollView>
    </SafeAreaView>
  )
}

export default App

Next steps

Next, we will learn the Introduction to Testing React Native applications.