Search⌘ K
AI Features

Adding Our First Component

Explore how to create your first React component within a Rails application using JSX and TypeScript. Understand the use of props and component structure as you convert static HTML into interactive React elements for enhanced user experience on a concert seat selection page.

Using React for concert seats

Let’s take a moment to discuss what we’ll be doing with React. The concert display page was part of our original Rails app, but we haven’t looked at it closely yet. It currently has a grid of seats that we’d like to use to allow people to select which seat they want to purchase at the particular concert being displayed.

Right now, it’s a grid of squares in an HTML table:

HTML
<table class="table">
<tbody>
<% @concert.venue.rows.times do |row| %>
<tr>
<% @concert.venue.seats_per_row.times do |seat| %>
<% ticket = @concert.find_ticket_at(
row: row + 1,
number: seat + 1
) %>
<td>
<%= link_to(ticket_path(ticket.id),
method: :patch) do %>
<div class="<%= ticket.color_for(current_user) %>
p-4 m-2 border-black border-4 button
hover:bg-blue-300 text-lg">
<%= seat + 1 %>
</div>
<% end %>
</td>
<% end %>
</tr>
<% end %>
</tbody>
</table>

The page also has a pull-down menu to select the number of tickets, but we’ll get to that later.

The first thing we are going to do is convert this code so React can control that part of the page. Eventually we are going to add interactivity to allow a user to select seats, show a running total, and show the current status of seat purchases. First, let’s simply draw the table in React.

This feature has great potential for React because it’s interactive but doesn’t use traditional browser form elements that might already have built-in browser functionality.

React encourages us to split our page into small components. To draw this section of the page, we’re going to have three components: one for each individual seat, one for each row that contains a row’s worth of seats, and one for the venue itself, which combines all the rows.

Here’s a screenshot of the page with the boundaries of our eventual React components, “Venue”, “Row”, and “Seat”, marked:

The application would look something like this:

module.exports = {
  purge: [],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [
    require('@tailwindcss/forms'),
    require('@tailwindcss/typography'),
    require('@tailwindcss/aspect-ratio'),
  ],
}

The Seat component

Let’s work from the inside out, the smallest component being the Seat. We’ve put all our React components in their own subdirectory of app/packs, which are named components. Here’s what the Seat component file looks like:

TypeScript 3.3.4
import * as React from "react"
interface SeatProps {
seatNumber: number
}
const Seat = (props: SeatProps): React.ReactElement => {
return (
<td>
<span
className="p-4 m-2 border-black border-4 button
hover:bg-blue-300 text-lg">
{props.seatNumber + 1}
</span>
</td>
)
}
export default Seat

The core of this file is the actual component, which is the constant function Seat. Notice that the syntax inside the Seat function does not look like standard JavaScript or TypeScript. Instead we have a <td>, and it looks like we dropped HTML directly into our TypeScript file, though somehow it’s all working.

That syntax is JSX. JSX is an extension to JavaScript that allows HTML-like elements inside angle brackets to be included in JavaScript or TypeScript code. React uses JSX to define HTML tags that are part of our React components and as a way of calling React components from other React components.

The React JSX parser converts our .jsx and .tsx files to plain JavaScript or TypeScript. This conversion has already been set up for us by Webpacker using a mechanism we’ll discuss in more detail in webpack and Webpacker.

Functional vs Class Components: React components can either be functions that return a JSX element or a class that contains a render method that returns a JSX element. Until recently, only class components could maintain an internal state, but that changed in React 16.8 with the introduction of hooks. The clear direction of the React team is that functional components are the future, so that’s what we’ll deal with here, but older React code has many class components.

Using JSX

Looking at our Seat function, we see that it takes one argument called props, but currently does nothing and returns an element that uses JSX to describe an HTML table cell. The return value encloses the JSX in parentheses, which is a common idiom for returning multiline JSX elements, allowing us to start the JSX element on the line after the return statement and preserve logical indenting.

The returned value starts with <td>. When a JSX file sees an angle bracket, it looks to convert the expression contained by that angle bracket element to a React element using an internal React function. JSX is mostly used as a shortcut instead of calling React’s tag creation functions throughout the program, which would be very verbose.

If the initial text inside the angle brackets, in this case td, starts with a lowercase letter, React assumes it is an HTML element. If it starts with an uppercase letter, React assumes it’s another React component.

Using JSX, we can include HTML attributes in our React components almost identically to how we would in normal HTML markup. Note one of the main exceptions in this code. Since the class is a reserved word in JavaScript, DOM classes are added to elements using the attribute name className.

Inside our span element we have {props.seatNumber}. This syntax uses two parts of React: props and the use of the curly brace.

The curly brace is React’s interpolation marker, similar to the way <%= %> is used in ERB. Inside curly braces, arbitrary JavaScript expressions are executed and passed through to the resulting element. In this case, we’re taking the seat number and rendering each Seat component using its actual seat number.

Props

Props, (short for “properties”), is React’s term for the attributes of a component that are meant to be stable, meaning that the component cannot change its props by itself. Props are passed into a component as a single argument when the component is created and are the only argument to a component. We could name the argument anything we want when defining the function, but using props is a convention.

We’re also using TypeScript to declare the type of our incoming props. In our case, we’ve declared SeatProps to say that the props object is expected to have only one key, seatNumber, and we expect that value to be a number. By declaring this interface, we allow TypeScript to do compile-time type checking to ensure that the necessary values are passed to our components.

We can then use the props object, as we do here, by interpolating props.seatNumber into our JSX. We’ll talk more about TypeScript interfaces in TypeScript.

Dereferencing
We’ll also often see JavaScript dereferencing syntax used to declare a component, where our declaration would be written as export const Seat = ({ seatNumber }). We could then use that value in the component directly, with <span className="button">{seatNumber}</span>, since the incoming props object will have been dereferenced directly to seatNumber.

The most important fact about props in a component is that a component cannot change its props once the component is instantiated. Changeable values in React components are called state and are handled differently. In the next lesson we’ll see how props are passed to a component, and then we’ll see one of the mechanisms React has to allow us to change stateful values.

That’s our first component. We added an export default statement to allow the Seat function to be imported easily by other files. As written, this prevents us from exporting the SeatProps type, but we don’t need it by name outside this file.

Here’s the application we have so far:

module.exports = {
  purge: [],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [
    require('@tailwindcss/forms'),
    require('@tailwindcss/typography'),
    require('@tailwindcss/aspect-ratio'),
  ],
}