Search⌘ K
AI Features

Adding Our First Component

Explore how to transform a static Rails seat grid into an interactive React component using JSX and props. Understand functional components, props handling, and TypeScript interface declarations to create dynamic UI parts within your Rails application.

Using React for concert seats

Let’s take a second and talk about how we’ll use React to do what we want to. The concert page was quietly 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 what seats they want to purchase at a particular concert.

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

HTML
<table class="table">
<tbody>
<% concert.venue.rows.times do %>
<tr>
<% concert.venue.seats_per_row.times do |seat_number| %>
<td>
<span class="button"><%= seat_number %></span>
</td>
<% end %>
</tr>
<% end %>
</tbody>
</table>

The first thing we are going to do is convert this partial to give React control of 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. But first we’re going to simply draw the table in React.

This feature has great potential for React because it is interactive, but yet does not use traditional browser form elements that might already have a lot of built-in functionality in the browser.

React encourages splitting your 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 Seat component

Let’s work from the most inside component up, which in our case is the Seat component. I’ve decided to put all our React components in their own subdirectory of app/javascript, which I have creatively named components. Here’s what the Seat component’s file looks like:

TypeScript 3.3.4
import * as React from "react"
interface SeatProps {
seatNumber: number
}
const Seat = (props: SeatProps) => {
return (
<td>
<span className="button">{props.seatNumber}</span>
</td>
)
}
export default Seat

The core of this file is the actual component, which is the constant function Seat on line 7. You’ll notice that the syntax inside the Seat function does not look like standard JavaScript or TypeScript. Instead, we have this weird <td> there, and it looks like we dropped HTML directly into our TypeScript file, and 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 also 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 I’ll discuss in more detail in the Webpack and the Webpacker chapter.

Functional vs Class Components React components can be either 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 internal state, but that changed in React 16.8 with the introduction of hooks. It seems to be the clear direction of the React team that functional components are the future, so that’s what we’ll deal with here, but if you look at a lot of older React code, you’ll see a lot of class components.

Using JSX

Looking at our Seat function, we see that it takes one argument called props and basically does nothing but return an element that uses JSX to describe an HTML table cell. The return value encloses the JSX in parenthesis, which is a common idiom for returning multi-line JSX elements to allow you 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 within that angle bracket element to a React element, using a React function called React.createElement.

Note that JSX is mostly a shortcut for calling React.createElement all over the place, which would feel horribly 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 do so in normal HTML markup. You can see one of the main exceptions in this code. Since 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

Which brings us to props. Props—short for “properties,” presumably—is React’s term for the attributes of a component that are meant to be stable, meaning that the component cannot change props 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; using props is a convention.

It’s a convention when using TypeScript to define an interface for the props object, which declares the keys of the prop object and their types. 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 the Typescript chapter.

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

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

And that is our first component. We added an export default statement to allow the Seat function to easily be imported by other files. As written, this actually 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:

Bud1
ocblob��appIlocblob����������������babel.config.jsIlocblob����������������binIlocblob����������������configIlocblob����������������	config.ruIlocblob����������������dbIlocblob����������������GemfileIlocblob����������������Gemfile.lockIlocblob����������������libIlocblob����������������package.jsonIlocblob����������������postcss.config.jsIlocblob����������������publicIlocblob����������������RakefileIlocblob����������������specIlocblob����������������storageIlocblob����������������
tsconfig.jsonIlocblob����������������vendorIlocblob����������������	yarn.lockIlocblob���������������� @� @� @� @E
DSDB `� @� @� @