0

I have an array of books which comes from an API.I want to filter the books array and put the selected book on a new array when the user selects a value from the dropdown.So,my book will have a dropdown which will have 3 values : 1)Currently Reading 2) Want to Read and 3) Read. Now I have created 3 arrays in my react state. So,when the user selects a value from the dropdown, it moves the book to the array defined for it.I have a handleChange() function for that purpose which will get the value of the array but I am not able to figure out how to remove the book selected from the books array and move it to the selected option array. So how do I filter out the books array based on the condition and put it to the new array.

Updated BooksList Code:

import React, { Component } from 'react';

class BooksList extends Component {
    constructor(props) {
      super(props);
      this.state={
        showSearchPage: false,
        books: this.props.books.map(book => Object.assign({}, book, {status:"none"}))
      };
        this.handleChange = this.handleChange.bind(this);
      }



  handleChange=(index,event) => {
    let books = this.state.books;
    books[index].status = event.target.value;
    this.setState({ books });
  }

  componentWillReceiveProps(nextProps) {
  if (this.props.books !== nextProps.books) {
    this.setState({ books: nextProps.books.map(book => Object.assign({}, book, { status: "none" })) });
  }
}

  render() {
    return(
        <div className="app">
          {this.state.showSearchPage ? (


            <div className="search-books">
              <div className="search-books-bar">
                <a className="close-search" onClick={() => this.setState({ showSearchPage: false })}>Close</a>
                <div className="search-books-input-wrapper">
                  {
                  <input type="text" placeholder="Search by title or author"/>

                </div>
              </div>
              <div className="search-books-results">
                <ol className="book-search">

                {<div className="book-search">
                  {this.state.books.map( (book,index) =>

                    <div key={index} className="book">
                      <div className="book-top">
                        <div className="book-cover" style={{ width: 128, height: 193,margin:10, backgroundImage: `url(${book.imageLinks.smallThumbnail})` }}></div>
                        <div className="book-shelf-changer">
                          <select
                            value={book.status}
                            onChange={(event) => this.handleChange(book.index,event)}>
                            <option value="none" disabled>&nbsp; &nbsp; Move to...</option>
                            <option value="currentlyReading">&#x2714; Currently Reading</option>
                            <option value="wantToRead">&nbsp; &nbsp; Want to Read</option>
                            <option selected="selected" value="read">&nbsp; &nbsp; Read</option>
                            <option value="none">&nbsp; &nbsp; None</option>
                          </select>
                        </div>
                      </div>
                      <div className="book-title">{book.title}</div>
                      <div className="book-authors">{book.authors}</div>
                      <p>{book.status}</p>
                    </div>
              )}
              </div>
                }

App.js:

import React from 'react'
// import * as BooksAPI from './BooksAPI'
import './App.css'
import * as BooksAPI from './BooksAPI'
import BooksList from './BooksList'

class BooksApp extends React.Component {
  state = {
    showSearchPage: false,
    selectValue: 'None',
    books: []
  }

  componentDidMount() {
    BooksAPI.getAll().then((books) => {
      this.setState({ books })
      console.log(books[0])
    })

  }

  render() {
    return (
      <BooksList books={this.state.books}/>
    )
  }
}

export default BooksApp

So how do I write my handleChange method to get the selected array?

Edit 1: Updated the code.

So, below is the error I get in the console:

Uncaught TypeError: Cannot set property 'status' of undefined
    at BooksList._this.handleChange (BooksList.js:17)
    at Object.executeOnChange (LinkedValueUtils.js:130)
    at ReactDOMComponent._handleChange (ReactDOMSelect.js:188)
    at HTMLUnknownElement.boundFunc (ReactErrorUtils.js:63)
    at Object.ReactErrorUtils.invokeGuardedCallback (ReactErrorUtils.js:69)
    at executeDispatch (EventPluginUtils.js:83)
    at Object.executeDispatchesInOrder (EventPluginUtils.js:106)
    at executeDispatchesAndRelease (EventPluginHub.js:41)
    at executeDispatchesAndReleaseTopLevel (EventPluginHub.js:52)
    at Array.forEach (<anonymous>)

Edit 2: Added the App.js file that contains the ComponentDidMount lifecycle method:

1
  • See if the 3 dropdown values are user specific that means user should have told you already that he/she wants to read xyz book or has read or currently reading it. Now you should have stored those details like ids of book somewhere. You can use those details to get other details of the book from the main array. Commented Oct 12, 2017 at 2:26

1 Answer 1

1

My advice is, don't do it this way. Instead, create a smarter books array that holds an internal state for each individual book, and work with that:

If the books data from the API are passed as props:

constructor(props) {
  super(props);

  state = {
    showSearchPage: false,
    // ES7 version
    books: this.props.books.map(book => {...book, status: "none"})
    // Non-ES7 version:
    // books: this.props.books.map(book => Object.assign({}, books, { status: "none" }))
  };

  // bind the event handler to the component
  this.handleChange = this.handleChange.bind(this);
}

And the dropdown event handler:

handleChange(index, event) {
  // grab a local copy of the book array that we will mutate
  let books = this.state.books;
  // only mutate the book that is being changed
  books[index].status = event.target.value;
  // safely pass the mutated copy into the state
  this.setState({ books });
}

Finally, in your render method:

{ this.state.books.map((book, index) => {
  return (
    <div key={index} className="book">
      ...other code here...
      <select
        value={book.status}
        onChange={(event) => this.handleChange(index, event)}>
      ...rest of your code...
    </div>
  );
})}

Lifecycle method to handle props update:

componentWillReceiveProps(nextProps) {
  if (this.props.books !== nextProps.books) {
    this.setState({ books: nextProps.books.map(book => Object.assign({}, book, { status: "none" })) });
  }
}

Edit: added binding to the component's this to the event handler in the constructor, and changed the way the handler is passed to the onChange event.

Edit 2: Added componentWillReceiveProps lifecycle hook

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

28 Comments

First of all,thanks for the answer but I am not able to understand what's going on in the code.Would you mind explaining a little bit.Sorry,i am new to React and Javascript.So, if you break it a down,I would really appreciate it.
First, the books array you pass as props (and that I assume you got from an API call earlier) is "enhanced" in the constructor by turning each individual book value into an object that holds the book value and a status that is by default set to "none" (which is the default value of your dropdown). In short, we're associating a default starting status to each book. The other thing we do in the constructor is bind the event handler to the component, so that when it is called later, the this correctly refers to the component.
In the render method, we change the array being iterated from this.props.books to this.state.books (our enhanced array). This is important. Then, in the select element, we set the default value to whatever the current status of the book being iterated is with value = {book.status}. Finally, we hook our handleChange method to the onChange event, and we pass the current book index as an additional parameter.
So, for a book, if I select a value from the dropdown,that selected value will be the value of status for that particular book right? So,if I want to pass the book to a different view and remove from the main view according to the status,how can i do that?I mean i have 3 different places where the books needs to be placed according to the values selected
Finally, in the event handler handleChange, we create a local copy of our smart books array (remember the state itself is not directly mutable), then we update the book's status using its index and setting the value to whatever the dropdown was set to (notice the other books are unaffected, this is very important). And finally we update the state with our modified books array.
|

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.