I am refactoring a small app that organises books onto respective shelves. I have extracted the JSX into a Book.js file to handle rendering each book object and call the updateShelf method to move books between shelves.
To that end, I have exported the updateShelf function from App.js and imported it into the Book.js component which is embedded in the ListBooks which uses the map function to pass each book object to Book to be rendered.
The issue I have is that when the onChange handler in Book fires I get the below exception:
TypeError: Object(...) is not a function
17 | <div className="book-top">
18 | <div className="book-cover" style={{ width: 128, height: 193, backgroundImage: `url(${book.imageLinks.thumbnail})` }}></div>
19 | <div className="book-shelf-changer">
> 20 | <select value={book.shelf} onChange={(e) => updateShelf(book, e.target.value)}>
21 | <option value="none" disabled >Move to...</option>
22 | <option value="currentlyReading" >Currently Reading</option>
23 | <option value="wantToRead" >Want to Read</option>
I am not sure what it is that I am doing wrong.
I've included quite a lot because I'm not sure exactly where the issue issue's source is. Let me know if I need to trim down the post.
App.js
import React, { Component } from 'react'
import ListBooks from './ListBooks'
import SearchBooks from './SearchBooks'
import * as BooksAPI from './utils/BooksAPI'
import { Route } from 'react-router-dom'
class BooksApp extends Component {
state = {
books: []
}
componentDidMount() {
BooksAPI.getAll()
.then((books) => {
this.setState(() => ({
books
}))
})
}
updateShelf(book, shelf) {
this.state.books.forEach(b => {
if(b.id === book.id && shelf !== '' && shelf !== 'none') {
b.shelf = shelf
this.setState((currentState) => ({
books: currentState.books
}))
BooksAPI.update(book, shelf)
}
});
}
render() {
return (
<div>
<Route exact path='/' render={() => (
<ListBooks
books={this.state.books}
onUpdateShelf={this.updateShelf}
/>
)} />
<Route exact path='/search' render={() => (
<SearchBooks
books={this.state.books}
/>
)} />
</div>
)
}
}
export default BooksApp
export const updateShelf = updateShelf;
ListBooks.js
import React, { Component } from 'react';
import PropTypes from 'prop-types'
import './App.css'
import { Link } from 'react-router-dom'
import Book from './Book'
const shelves = [
{
key: 'currentlyReading',
name: 'Currently Reading'
},
{
key: 'wantToRead',
name: 'Want To Read'
},
{
key: 'read',
name: 'Read'
}
];
class ListBooks extends Component {
static propTypes = {
books: PropTypes.array.isRequired
}
render() {
const { books } = this.props
function getBooksForShelf(shelfKey) {
return books.filter(book => book.shelf === shelfKey);
}
return(
<div className="app">
<div className="list-books">
<div className="list-books-title">
<h1>My Reads</h1>
</div>
<div className="list-books-content">
<div>
{ shelves.map((shelf) => (
<div key={shelf.key} className="bookshelf">
<h2 className="bookshelf-title">{shelf.name}</h2>
<div className="bookshelf-books">
<ol className="books-grid">
<li>
{ getBooksForShelf(shelf.key).map((book) => (
<Book key={book.id} book={book}/>
))}
</li>
</ol>
</div>
</div>
)) }
</div>
</div>
<Link
to='/search'
className="open-search">
Find a Book
</Link>
</div>
}
</div>
)
}
}
export default ListBooks
Book.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { updateShelf } from './App'
class Book extends Component {
static propTypes = {
book: PropTypes.object.isRequired
}
render() {
const { book } = this.props
return(
<div key={book.id} className="book">
<div className="book-top">
<div className="book-cover" style={{ width: 128, height: 193, backgroundImage: `url(${book.imageLinks.thumbnail})` }}></div>
<div className="book-shelf-changer">
<select value={book.shelf} onChange={(e) => updateShelf(book, e.target.value)}>
<option value="none" disabled >Move to...</option>
<option value="currentlyReading" >Currently Reading</option>
<option value="wantToRead" >Want to Read</option>
<option value="read" >Read</option>
<option value="none" >None</option>
</select>
</div>
</div>
<div className="book-title">{book.title}</div>
<div className="book-authors">{book.authors}</div>
</div>
)
}
}
export default Book