Building Components page
Learn about components, the core building blocks of every React application.
Overview
In this section, we will:
- Learn the basics of creating components in React
- Discover how components are structured
- Review how React components are (fundamentally) functions
Objective: Create a React component
Our App
component currently shows our restaurant list, but eventually we’ll want to show other page content. Let’s prepare now by moving all of the JSX from App
to a new component called RestaurantList
.
In our Place My Order app, we want to:
- Create our first brand new React component (RestaurantList)
- Move the logic from our App component to our new RestaurantList component
What are components?
So far, we have placed all of our JSX inside the App
function. Notice two things about the App
function:
- The name starts with a capital letter
- It returns something renderable (JSX)
function App() {
return <main>Some page content</main>
}
In React, we call this a component. When you create a component in React, you are creating building blocks that can be composed, reordered, and reused much like HTML elements.
React makes it relatively straightforward to create new components. Let’s learn to build our own.
Component structure
Let’s start by creating a component from a commonly reused element, the button.
First, React component names must start with a capital letter, so we can call this Button
. By convention component names use PascalCase when naming components, so longer component names will look like IconButton
. Avoid hyphens and underscores.
Second, our component must return either null
or something renderable, like JSX. The return value of our components is almost always JSX, though JavaScript primitives like string
and number
are also valid. Components cannot return complex types like arrays or objects.
const Button = () => {
return <button className="button primary">Activate me</button>
}
Components are like small containers which can be reused throughout your application. The Button
component above returns JSX and could then be rendered and reused by another component like App
below.
const App = () => {
return (
<main>
<Button />
<Button />
<Button />
</main>
)
}
React components are functions
The JSX syntax allows function components to look like HTML, but underneath they are still functions. The return of each component is unique and you can use the same component multiple times.
You can think of components as fancy functions.
While you can’t actually do the following, this is functionally similar to what React is doing for you.
import type React from "react"
const App: React.FC = () => {
return (
<main>
{Button()}
{Button()}
{Button()}
</main>
)
}
Did you notice the React.FC
that was used in the previous example to type the App
const? Because we’re using TypeScript with our project, we can apply types to help make sure the function component is properly formed. React provides the type FC
(an abbreviation for "function component") that can be applied to a function component. This type defines the arguments and return value that a function component must implement.
Setup
It’s best practice to create a new folder that will contain all of the related files for that component, including test and CSS files.
✏️ Create src/pages/ (folder)
✏️ Create src/pages/RestaurantList/ (folder)
✏️ Create src/pages/RestaurantList/index.ts and update it to be:
export { default } from "./RestaurantList"
✏️ Create src/pages/RestaurantList/RestaurantList.tsx and update it to be:
import CheeseThumbnail from "place-my-order-assets/images/2-thumbnail.jpg"
import PoutineThumbnail from "place-my-order-assets/images/4-thumbnail.jpg"
const RestaurantList: React.FC = () => {
return <></>
}
export default RestaurantList
✏️ 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 RestaurantList from "./pages/RestaurantList"
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
Verify
✏️ Create src/pages/RestaurantList/RestaurantList.test.tsx and update it to be:
import "@testing-library/jest-dom"
import { render, screen } from "@testing-library/react"
import { describe, expect, it } from "vitest"
import RestaurantList from "./RestaurantList"
describe("RestaurantList component", () => {
it("renders the Restaurants header", () => {
render(<RestaurantList />)
expect(screen.getByText(/Restaurants/i)).toBeInTheDocument()
})
it("renders the restaurant images", () => {
render(<RestaurantList />)
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(<RestaurantList />)
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(<RestaurantList />)
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(<RestaurantList />)
const openNowTags = screen.getAllByText("Open Now")
expect(openNowTags.length).toBeGreaterThan(0)
})
it("renders the details buttons with correct links for each restaurant", () => {
render(<RestaurantList />)
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")
})
})
})
✏️ Update src/App.test.tsx to be a simple smoke test:
import "@testing-library/jest-dom"
import { render, screen } from "@testing-library/react"
import { describe, expect, it, vi } from "vitest"
import App from "./App"
// Mocking RestaurantList component
vi.mock("./pages/RestaurantList", () => ({
__esModule: true,
default: () => <div>Mocked Restaurant List</div>,
}))
describe("App Component", () => {
// Testing if the App component renders without crashing
it("renders without crashing", () => {
render(<App />)
expect(screen.getByText("Mocked Restaurant List")).toBeInTheDocument()
})
})
Exercise
- Move the logic in our
App
component to our newRestaurantList
component. - Update our
App
component to use our newRestaurantList
component.
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 RestaurantList from "./pages/RestaurantList"
import "./App.css"
function App() {
return (
<>
<RestaurantList />
</>
)
}
export default App
✏️ Update src/pages/RestaurantList/RestaurantList.tsx
import CheeseThumbnail from "place-my-order-assets/images/2-thumbnail.jpg"
import PoutineThumbnail from "place-my-order-assets/images/4-thumbnail.jpg"
const RestaurantList: React.FC = () => {
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 RestaurantList
Having issues with your local setup? See the solution in StackBlitz or CodeSandbox.
Next steps
Next we’ll learn to pass arguments to our components through props.