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.
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.
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.
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 AuthorListIts 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.
.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.
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 BookListAs with AuthorList, the book card styling is co-located in its own BookList.css and scoped under .book-list / .book.
.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.
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.
main { max-width: 720px; margin: 0 auto; padding: 2rem 1rem; font-family: system-ui, sans-serif; line-height: 1.5;}
h1 { margin-bottom: 2rem;}