0

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

1 Answer 1

1

You're exporting export const updateShelf = updateShelf. But updateShelf is a part of the Component, so it's most likely undefined. You should move the method outside of the class if you plan to export it, or pass it down as a prop if you expect it to change the component.

Sign up to request clarification or add additional context in comments.

2 Comments

That did the job. I had to pass it via ListBooks.js component though. Is there a way to pass it direct to Book.js?
You can use the React Context API. But I would recommend passing it through ListBooks.js for now.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.