0

Apologies if this question has been asked before and solved, but I have been searching and haven't had much luck.

I have created an app where a person can search for a book and the results are returned. The problem I am having is when I enter a book title and select the search button an empty array is rendered and then an array with the proper results are returned but not rendered. When I select the search button again the proper results are rendered. I would like the correct results to be rendered on the first click.

I logged the results and it looks like the empty array is being rendered first instead of the correct results. I believe it could be due to the async nature of the API call.

Here is my code:

class App extends Component {

  state = {
    data: []
  }

  handleBookSearch = (book) => {
    let data = utils.searchBooks(book);
    this.setState({ data: data });
  }

  render() {
    return (
      <div className="">
        <Banner
          onSearch={this.handleBookSearch}
          onRender={this.renderBooks} />
        <div className="custom-margin">

          <BookDisplay bookData={this.state.data} />
        </div>
      </div>
    );
  }
}

export default App;

class PostDisplay extends Component {
    render() {
        const { bookData } = this.props;


        return (
            bookData.map(book => (
                <div>
                    <div className="cap-color">
                        <h4 className="card-header"><i className="fas fa-book fa-fw"></i>{book.title}</h4>
                    </div>
                    <div className="card-body">
                        <img src={book.thumbnail} alt={book.title} />
                        <h5 className="card-title margin-above"><b>Author:</b> {book.authors}</h5>
                        <h6><b>Publisher:</b> {book.publisher}</h6>
                        <h6><b>Published On:</b> {book.publishedDate}</h6>
                        <h6><b>Supported Languages:</b> {book.language}</h6>
                        <p className="card-text"><b>Description:</b> {book.description}</p>
                        <a href={book.link} target="_blank" rel="noopener noreferrer"
                            className="btn btn-primary cap-theme-project">Buy the book!</a>
                    </div>
                </div>
            ))
        );
    }
}

export default PostDisplay;

class Banner extends Component {
    constructor() {
        super();
        this.state = {
            search: ''
        };
    }

    updateSearch(event) {
        const searchParam = event.target.value;
        this.setState({ search: searchParam });
    }

    render() {
        return (
            < div className="title-banner" >
                <h1 className="header-padding"><i className="fas fa-book fa-fw"></i><b>Google Library</b></h1>
                <div className="container">
                    <div className="row justify-content-center">
                        <div className="col-12 col-md-10 col-lg-8 opacity">
                            <div className="card-body row no-gutters align-items-center">
                                <div className="col">
                                    <input className="form-control form-control-lg form-control-borderless"
                                        type="text" placeholder="Search for a book..." ref="searchQuery"
                                        value={this.state.search}
                                        onChange={this.updateSearch.bind(this)} />
                                </div>
                                <div className="col-auto">
                                    <button onClick={() => this.props.onSearch(this.state.search)}
                                        className="btn-margin btn btn-lg btn-primary"><i class="fas fa-search"></i></button>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div >
        );
    }
}

export default Banner;

var books = require('google-books-search');

export let data = [];

export function searchBooks(title) {
    books.search(title, function (err, results) {
        if (!err) {
            data = results;
        } else {
            console.log('ERROR: ' + err);
        }
    });
    return data;
}
erae02
  • 3
  • 1
  • Possible duplicate of [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Agney Dec 24 '18 at 03:43
  • Possible duplicate of [setState doesn't update the state immediately](https://stackoverflow.com/questions/41278385/setstate-doesnt-update-the-state-immediately) – Pragyakar Dec 24 '18 at 05:53

2 Answers2

0

Yes your assumption is correct it is due to async operation. Handle promise like this.

 handleBookSearch = async (book) => {
   let data = await utils.searchBooks(book);
   this.setState({ data: data });
 }
Anil Kumar
  • 2,223
  • 2
  • 17
  • 22
  • Hello Anil. Although that method makes sense and should work, it still requires me to select the 'search' button twice. – erae02 Dec 24 '18 at 20:54
0

Use callback function. It will set state after callback method called.

handleBookSearch = (book) => {
    let data = utils.searchBooks(book,(data)=>{
       this.setState({ data: data });
    });
}

export function searchBooks(title,callback) {
    books.search(title, function (err, results) {
        if (!err) {
            callback(results);
        } else {
            console.log('ERROR: ' + err);
        }
    });
}