C

Create Components

In this lesson we build our first real UI out of components. Starting from the empty App scaffold, we model some typed data, create two reusable list components — AuthorList and BookList — and compose them together on the main page. Along the way we cover the core building blocks of React: typed props, rendering lists with .map, the key prop, and co-locating each component’s styles in its own CSS file.

The finished project lives in courses-code/react/components.

1.Model the data

Before rendering anything we describe the shape of our data with TypeScript types, then provide a small sample data set the components will display.

`src/types.ts`

Two interfaces describe the domain. A Book references its authors, so Book reuses the Author type — this is the same data the Spring Boot API returns, modeled on the client.

src/types.ts
export interface Author {
id: string
name: string
}
export interface Book {
id: string
title: string
isbn: string
description: string
authors: Author[]
publicationDate: string
}

`src/data.ts`

A hard-coded set of authors and books typed with the interfaces above. Keeping the data in its own module lets the components stay focused purely on rendering — later this is exactly where a real API call would plug in.

src/data.ts
import type { Author, Book } from './types'
export const authors: Author[] = [
{
"id": "a430664d-f158-4611-bbc8-9c27f458a0f5",
"name": "Craig Walls"
},
{
"id": "9fe20c78-8844-4ee2-80b6-8b9eb93a1738",
"name": "Benjamin J. Evans"
},
{
"id": "a150fde5-6f92-4a69-b5aa-883afb368621",
"name": "Jason Clark"
},
{
"id": "a9903d38-591b-40d0-b1f8-b16d9b2bcf63",
"name": "Martijn Verburg"
}
]
export const books: Book[] = [
{
"id": "784909ca-a548-4f27-86fa-eb062759f5cd",
"title": "Spring in Action, Sixth Edition",
"isbn": "978-1617297571",
"description": "A comprehensive, hands-on guide to building reactive and cloud-native applications with Spring and Spring Boot.",
"authors": [
{
"id": "a430664d-f158-4611-bbc8-9c27f458a0f5",
"name": "Craig Walls"
}
],
"publicationDate": "2022-01-25"
},
{
"id": "2e040e97-f76b-4a99-89c3-3bbefde76f87",
"title": "The Well-Grounded Java Developer, Second Edition",
"isbn": "978-1617298875",
"description": "Updated for Java 17, a deep dive into modern Java covering the JVM, concurrency, modules, and alternative JVM languages.",
"authors": [
{
"id": "9fe20c78-8844-4ee2-80b6-8b9eb93a1738",
"name": "Benjamin J. Evans"
},
{
"id": "a9903d38-591b-40d0-b1f8-b16d9b2bcf63",
"name": "Martijn Verburg"
},
{
"id": "a150fde5-6f92-4a69-b5aa-883afb368621",
"name": "Jason Clark"
}
],
"publicationDate": "2022-07-26"
}
]

2.Create the list components

Each component receives its data through props and renders a list. Notice the recurring pattern: type the props with an interface, destructure them in the function signature, and turn an array into JSX with array.map(...).

`AuthorList`

Takes an authors: Author[] prop and renders each author’s name as a list item. The AuthorListProps interface makes the contract explicit and gives us autocomplete and type-checking at every call site.

src/components/AuthorList.tsx
import type { Author } from '../types'
import './AuthorList.css'
interface AuthorListProps {
authors: Author[]
}
function AuthorList({ authors }: AuthorListProps) {
return (
<section className="author-list">
<h2>Authors</h2>
<ul>
{authors.map((author) => (
<li key={author.id}>{author.name}</li>
))}
</ul>
</section>
)
}
export default AuthorList

Its styles live next to the component in AuthorList.css, imported at the top of the file. The selectors are scoped under .author-list so they only affect this component.

src/components/AuthorList.css
.author-list ul {
list-style: none;
padding: 0;
margin: 0;
}
.author-list li {
padding: 0.25rem 0;
}

`BookList`

Takes a books: Book[] prop and renders a richer card for each book — title, description, and a definition list of metadata (ISBN, publication date, authors). Because a book’s authors is itself an array, we map it to names and join them with ", " for display.

src/components/BookList.tsx
import type { Book } from '../types'
import './BookList.css'
interface BookListProps {
books: Book[]
}
function BookList({ books }: BookListProps) {
return (
<section className="book-list">
<h2>Books</h2>
<ul>
{books.map((book) => (
<li key={book.id} className="book">
<h3>{book.title}</h3>
<p className="book-description">{book.description}</p>
<dl className="book-meta">
<dt>ISBN</dt>
<dd>{book.isbn}</dd>
<dt>Published</dt>
<dd>{book.publicationDate}</dd>
<dt>Authors</dt>
<dd>{book.authors.map((author) => author.name).join(', ')}</dd>
</dl>
</li>
))}
</ul>
</section>
)
}
export default BookList

As with AuthorList, the book card styling is co-located in its own BookList.css and scoped under .book-list / .book.

src/components/BookList.css
.book-list ul {
list-style: none;
padding: 0;
margin: 0;
}
.book-list .book {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem 1.25rem;
margin-bottom: 1rem;
}
.book h3 {
margin: 0 0 0.5rem;
}
.book-description {
margin: 0 0 0.75rem;
}
.book-meta {
display: grid;
grid-template-columns: max-content 1fr;
gap: 0.25rem 1rem;
margin: 0;
font-size: 0.9rem;
}
.book-meta dt {
font-weight: 600;
}
.book-meta dd {
margin: 0;
}

3.Render them on the page

Finally App ties everything together: it imports the data and the two components, then renders each one, passing the matching array as a prop. This is component composition — a parent component assembling smaller child components into a page.

`src/App.tsx`

Imports authors and books from the data module and hands them to <AuthorList> and <BookList>. App owns the data and the children own the rendering — a clean separation of responsibilities.

src/App.tsx
import AuthorList from './components/AuthorList'
import BookList from './components/BookList'
import { authors, books } from './data'
import './App.css'
function App() {
return (
<main>
<h1>Library</h1>
<AuthorList authors={authors} />
<BookList books={books} />
</main>
)
}
export default App

`src/App.css`

App-level layout only — the page container (main) and the top heading (h1). Component-specific rules deliberately live in each component’s own stylesheet rather than here, so styles stay close to the markup they affect.

src/App.css
main {
max-width: 720px;
margin: 0 auto;
padding: 2rem 1rem;
font-family: system-ui, sans-serif;
line-height: 1.5;
}
h1 {
margin-bottom: 2rem;
}