Step 8

Update a book

We just added a page to create a new book, but we also want to update an existing book.

Let's create our EditBook container

src/containers/EditBook.tsx

import React, { Component, ChangeEvent, FormEvent } from 'react';
import { fetchBook, updateBook } from '../actions';
import { RouteComponentProps } from 'react-router-dom';
import BookForm from '../components/BookForm';

type Props = RouteComponentProps<{ id: string }>;

export default class EditBookPage extends Component<Props> {
	state = {
		title: '',
		author: '',
		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 books:', error);
			this.setState({ loading: false });
		}
	}

	onChange = (e: ChangeEvent<HTMLInputElement>) =>
		this.setState({ [e.target.name]: e.target.value });

	onSubmit = async (e: FormEvent<HTMLFormElement>) => {
		try {
			e.preventDefault();
			const {
				match: {
					params: { id }
				},
				history: { push }
			} = this.props;
			const { title, author } = this.state;
			await updateBook(id, { title, author });
			push('/books');
		} catch (error) {
			console.error('Error updating book:', error);
		}
	};

	render() {
		const { loading, title, author } = this.state;
		if (loading) return <div>Loading...</div>;
		if (!loading && !title && !author) return <div>Book not found!</div>;
		return (
			<div>
				Update this book:
				<BookForm
					{...this.state}
					onSubmit={this.onSubmit}
					onChange={this.onChange}
				/>
			</div>
		);
	}
}

This combines the concepts we learned from our BookPage and CreateBookPage containers, so this should look familiar.

Now, we add the route to 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';
import CreateBookPage from './containers/CreateBook';
import EditBookPage from './containers/EditBook';

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

export default App;

We can also add a button to our Book page to edit it.

src/containers/Book.tsx

import React, { Component } from 'react';
import { fetchBook } from '../actions';
import Book from '../components/Book';
import { RouteComponentProps, Link } 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>}
				<Link to={`/books/${this.props.match.params.id}/edit`}>
					<button>Edit</button>
				</Link>
			</div>
		);
	}
}

Last updated

Was this helpful?