React - Cannot read property 'map' of undefined when state is updated

The following code works only with one letter searchs or less (0 letters), i don´t understand what is happening, but apparently it´s a problem in the state update.

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import * as BooksAPI from './BooksAPI';
import { observable } from 'mobx';
import { observer } from 'mobx-react';


const BookSearch = observer(class BookSearch extends Component{

	state = observable({
		query: '',
		books: []
	})

	searchBooks = (query) =>{
		console.log(query.target.value)
		this.state.query = query.target.value

		BooksAPI.search(query.target.value, 20).then((books) =>{
	    	console.log(books)
	    	this.state.books = books;
			console.log(this.state.books)
		})
		console.log(this.state.books)
		
 	}

	render(){

		const { query } = this.state;

		const bookSearch =  this.state.books.map((book) => 
				<li key={book.id}>
					<div 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 onChange={(event) => this.props.bookChange(book, event.target.value)}>
									<option>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.map((author) => 
							<span key={ author + book.title } >{ author }</span>
						)}</div>
					</div>
				</li>
			) ;

		
		return(
			
			<div>	
			<div className="search-books">
            	<div className="search-books-bar">
              		<Link className="close-search" to='/'>Close</Link>
              		<div className="search-books-input-wrapper">
                		{/*
                  			NOTES: The search from BooksAPI is limited to a particular set of search terms.
                  			You can find these search terms here:
                  			https://github.com/udacity/reactnd-project-myreads-starter/blob/master/SEARCH_TERMS.md
		
                  			However, remember that the BooksAPI.search method DOES search by title or author. So, don't worry if
                  			you don't find a specific author or title. Every search is limited by search terms.
                		*/}
                		<input 
                  			type="text" 
                  			placeholder="Search by title or author" 	
                  			onChange={ this.searchBooks}
                		/>	
              		</div>
            	</div>
            	<div className="search-books-results">
              		<div className="bookshelf">
				<h2 className="bookshelf-title">{ this.state.query }</h2>
				<div className="bookshelf-books">
					<ol className="books-grid">
						{ bookSearch }
					</ol>
				</div>
			</div>
            </div>
			</div>
			
			</div>
		)
	}
})

I´m using mobx, but i had the same problems without it.

Don’t update state directly:

//wrong
this.state.something = value

for several reasons expressed in the documentation.

To update the state use setState function.
So that your function should look like:

	searchBooks = (query) =>{
		console.log(query.target.value)
		this.setState({ query: value });

		BooksAPI.search(query.target.value, 20).then((books) =>{
	    	console.log(books)
	    	this.setState({ books: books}); // probably just setState({ books }) will do
			console.log(this.state.books)
		})

Also mind that setState may be asynchronous so don’t rely too much on the last
console.log(this.state.books) to hold the correct value :slight_smile:

Better use some dev tools specific for React and inspect from there if the state updates correctly.

Hope it helps!

yeah i know that setState is asynchronous, but it doesn´t matter cause the problem happens before getting there, and react jumps out of the function.

About updating the state directly i´m doing that cause i´m using mobx, but when i was using setState the same problem was occurring.

I’ve used two way data binding with React recently also.

Let me know if that works… That would be my best guess. I believe the problem is that you’re not binding the data with .bind(this)… Also the react docs show the value defined explicitly in the input tag.

<input 
                  			type="text" 
                  			placeholder="Search by title or author" 	
                  			onChange={ this.searchBooks.bind(this)}
                                        value={this.state.query}
                		/>

At this point I feel the issue is related more to MobX than React itself.

It would be nice to have a working demo so that I can help you debug it; so far this it what I would look into it:

1 - No usage of decorators
this means that the syntax for declaring observables is different ( I fear you have to bind this, but I’m not sure, refer to MobX docs on the usage without decorators.

EDIT2 - clarification on this point:
2 - React won’t re-render from direct state update like that, so if MobX observable has something wrong (point 1) we cannot rely on this.state to actually cause a re-render.

Sorry if I cannot be of more help but besides some prototyping I’ve never used MobX :frowning:

EDIT:

I´m using mobx, but i had the same problems without it.

If you don’t mind I’d like to see the non-mobx version :slight_smile:


p.s. the setState is async note was related to the console.log() that was following it, not the issue itself.

So here is the code for the bookSearch function.

searchBooks = (query) =>{
    this.setState({ query: query.trim() });

    BooksAPI.search(query, 20).then((books) =>{
      console.log(books)
      this.setState({ books: books });
    })
    
  }

and here is an image of the program running, maybe it helps clarify the problem :slight_smile: :

the first letter i digit goes fine, then the second returns a problem, and i don´t really know why.

thnks, but i´m still running in the same issue. :frowning:

As you can see from the inspector the error is coming from MobX, specifically from observablearray.

There’s definitely something wrong in the way your mobx is interacting with the app.

Have you tried using it with decorators?

My suggestion is to make the component without MobX first and see if everything works as expected. Then integrate it with a Store library to see what/where went wrong.

Here I created a sandbox with a bare minimum working app that lets you write in a (controlled) input and search for that user in the GithubAPI.

You have a different API endpoint but the mechanism should be the same.
Hope it helps :+1:

you can just wrap the .map block with a conditional statement.

if ( condition here ) {
      const bookSearch =  this.state.books.map((book) => 
				<li key={book.id}>
					<div 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 onChange={(event) => this.props.bookChange(book, event.target.value)}>
									<option>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.map((author) => 
							<span key={ author + book.title } >{ author }</span>
						)}</div>
					</div>
				</li>
			) ;

		
 }

Read this doc on StackOverflow: (https://stackoverflow.com/questions/24706267/cannot-read-property-map-of-undefined)