Step 6

Individual book page

We are currently fetching all books, but what if we only want to view a single book? Let's create a page for it.

src/containers/Book.tsx

import React, { Component } from 'react';
import { fetchBook } from '../actions';
import Book from '../components/Book';
import { RouteComponentProps } from 'react-router-dom';
import { IBook } from '../types';

type BookProps = RouteComponentProps<{ id: string }>;
type BookState = { book: IBook | null; loading: boolean };

export default class BookPage extends Component<BookProps, BookState> {
	state = {
		book: null,
		loading: true
	};

	async componentDidMount() {
		try {
			const {
				match: {
					params: { id }
				}
			} = this.props;
			const book = await fetchBook(id);
			this.setState({ book, loading: false });
		} catch (error) {
			console.error('Error fetching book:', error);
			this.setState({ loading: false });
		}
	}

	render() {
		const { book, loading } = this.state;
		if (loading) return <div>Loading...</div>;
		return (
			<div>
				The book you are looking for:
				{book ? <Book book={book} /> : <div>Book not found!</div>}
			</div>
		);
	}
}

This is similar to our Books page, but there are a few differences. We are utilizing Typescript and defining type definitions for what our props and state will look like. When React Router renders a component defined in a route, it also passes the component some props. The specific prop that we are concerned with is the id field in the params object, so we destructure it. We then use this id to fetch our book, and if there is no book associated with it, then tell the user "Book not found"

We can now use it in our App

src/App.tsx

import React, { Component } from 'react';
import './App.css';
import BooksPage from './containers/Books';
import { Switch, Route } from 'react-router-dom';
import HomePage from './containers/Home';
import Navbar from './components/Navbar';
import BookPage from './containers/Book';

class App extends Component {
	render() {
		return (
			<div className="App App-header">
				<Navbar />
				<br />
				<Switch>
					<Route exact path="/" component={HomePage} />
					<Route exact path="/books/:id" component={BookPage} />
					<Route exact path="/books" component={BooksPage} />
				</Switch>
			</div>
		);
	}
}

export default App;

The colon notation indicates that it is a URL parameter, which can be match that section of the URL to a variable. We use this "id" param in our Book page.

We can also create a link to each book in our books list:

src/components/BookList.tsx

import React from 'react';
import { Link } from 'react-router-dom';
import Book from './Book';
import { IBook } from '../types';

const BooksList = ({ books }: { books: IBook[] }) => {
	return (
		<div>
			{books.map((book: IBook) => (
				<div key={book.id}>
					<Link to={`/books/${book.id}`} className="App-link">
						<Book book={book} />
					</Link>
					<hr />
				</div>
			))}
		</div>
	);
};

export default BooksList;

Last updated

Was this helpful?