0

I am passing the state of the parent component from the parent component to the child component.And in the child component,I have a different state.I am performing some actions on the child component's state and the result of that has to be added to the parent component's state.So,in my parent component I have written a callback function which will update the state of the parent component.The code is:

updateState = (booksList) => {
          this.setState({books : this.state.books.push(booksList)});
        }

So,this function is then passed to the child component as props:

<BookSearch
   books={this.state.books}
   handleShelfChange={this.handleShelfChange}
   updateState={this.updateState}/>

Then in my child component,I am trying to implement the callback function as :

let getBook = this.state.books.filter(filteredBook => filteredBook.shelf !== "none")
    this.props.updateState(getBook)

But this is not working as expected.Is this the correct way?Can anyone please help me with this?

I have tried to solve my problem by implementing the solution provided here : How to pass data from child component to its parent in ReactJS? , but I am getting some errors.

EDIT

Parent component : App.js

import React from 'react'
import * as BooksAPI from './BooksAPI'
import { Link } from 'react-router-dom'
import { Route } from 'react-router-dom'
import './App.css'
import BookList from './BookList'
import BookSearch from './BookSearch'


class BooksApp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      books: [],
      showSearchPage : false
    };
    //this.updateState = this.updateState.bind(this)
  }

  componentDidMount() {
    BooksAPI.getAll().then((books) => {
      this.setState({ books })
    })
    console.log(this.state.books);
  }

  filterByShelf = (bookName,shelfName) =>
   bookName.filter(book => book.shelf===shelfName)


  isTheBookNew = book => {
    let is = false;
    if (book.shelf === "none")
     { this.setState(state =>
       {
         books: state.books.push(book)});
          is = true;
          console.log(this.state.books);
       }
       return is;
      };

      handleShelfChange = (bookOnChange, newSehlf) => {
         !this.isTheBookNew(bookOnChange) && this.setState(state => {
         let newBooks = state.books.map(book =>
           { if (bookOnChange.id === book.id)
             { book.shelf = newSehlf; }
             return book;
           });
             return {
                books: newBooks
               };
              }
            );
            BooksAPI.update(bookOnChange, newSehlf);
            };

        updateState = (booksList) => {
          const books = [...this.state.books, booksList]
          this.setState({ books });
        }

  render() {
    return (
      <div className="app">
        <Route exact path="/" render={() => (
          <div className="list-books">
              <div className="list-books-title">
                <h1>MyReads</h1>
              </div>

              <BookList
              books={this.filterByShelf(this.state.books,'currentlyReading')}
              shelfName='Currently Reading'
              handleShelfChange={this.handleShelfChange}/>

              <BookList
              books={this.filterByShelf(this.state.books,'wantToRead')}
              shelfName='Want to Read'
              handleShelfChange={this.handleShelfChange}/>

              <BookList
              books={this.filterByShelf(this.state.books,'read')}
              shelfName='Read'
              handleShelfChange={this.handleShelfChange}/>

              <div className="open-search">
                <Link
                to="./search" />
              </div>
          </div>
        )
      } />

            <Route path="/search" render={() =>
                <BookSearch
                 books={this.state.books}
                 handleShelfChange={this.handleShelfChange}
                 updateState={this.updateState}/>
              } />

      </div>
    )
  }
}

export default BooksApp

BookSearch.js :

import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import escapeRegExp from 'escape-string-regexp'
import  sortBy from 'sort-by'
import * as BooksAPI from './BooksAPI'
import BookList from './BookList'

class BookSearch extends Component {
  constructor(props) {
    super(props);
    this.state = {
      search:'',
      books:[]
    }
  }

  updateSearch = (searchString) => {
    this.setState({search: searchString.trim()})
    let searchResults = BooksAPI.search(this.state.search,1).then((book_search) => {
      if (book_search != undefined) {
        console.log(book_search);
          book_search.map((book) => book.shelf = 'none');
          this.setState({ books : book_search }, this.check); // callback function to this.setState
          console.log(this.state.books)
        }
    })

    }


  check = () => {
    let parent_books = this.props.books;
    console.log(this.state.books)
    const book_result = this.state.books.map((book) => {
      const parent = parent_books.find(parent => parent.title === book.title );
       if(parent) {
        //console.log(parent);
        book.shelf = parent.shelf;
        //console.log(book)
          }

        return book;
      })
    this.setState({books: book_result}, () => {console.log(this.state.books)})
  }

  updateParentState = () => {
    let getBook = this.state.books.filter(filteredBook => filteredBook.shelf !== "none")
    this.props.updateState(getBook)

  }

  render() {
    return(

      <div className="search-books">
        <div className="search-books-bar">
          <Link
            to="/"
            className="close-search">
            Close
          </Link>
          <div className="search-books-input-wrapper">
            <input
             type="text"
             placeholder="Search by title or author"
             value={this.state.search}
             onChange={(event) => this.updateSearch(event.target.value)}/>
          </div>
        </div>

        <div className="search-books-results">
          <ol className="books-grid">
            <BookList
              books={this.state.books}
              handleShelfChange={this.props.handleShelfChange}
              updateParentState={this.updateParentState}/>
          </ol>
        </div>
      </div>
    )
  }
}

export default BookSearch

BookList.js

import React, { Component } from 'react';
import Book from './Book'

class BookList extends Component {
constructor(props) {
  super(props);
  this.state = {
    showSearchPage : false
  }
  console.log(this.props.books)
}


  render() {
    return(
      <div className="app">
          <div>
           <div className="list-books-content">
              <div>
                <div className="bookshelf">
                  <h2 className="bookshelf-title">{this.props.shelfName}</h2>
                  <div className="bookshelf-books">
                    <ol className="books-grid">

                      {this.props.books.map(book =>
                        <li key={book.title}>
                          <Book
                          book={book}
                          handleShelfChange={this.props.handleShelfChange}
                          update={this.props.updateParentState} />
                        </li>)
                      }
                    </ol>
                  </div>

                </div>
              </div>
            </div>
          </div>
    </div>
    )
  }
}


export default BookList;

Book.js

import React, { Component } from 'react'

class Book extends Component {
  constructor(props) {
    super(props);
    this.props.updateParentState;
  }

  render() {
    return(
      <div className="book">
        <div key={this.props.book.title}>
          <div className="book-top">
            <div className="book-cover" style={{width:128, height:193, backgroundImage: `url(${this.props.book.imageLinks.thumbnail})`}}>
                  <div className="book-shelf-changer">
                    <select id="bookName" value={this.props.book.shelf}
                      onChange={(event) => this.props.handleShelfChange(this.props.book, event.target.value)}>
                      <option value="moveTo" 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>
          <div className="book-title">{this.props.book.title}</div>
          <div className="book-authors">{this.props.book.authors}</div>
        </div>

  </div>
      )
  }
}

export default Book

So, I have 4 components as shown above.From App component,I am calling BookSearch component where I can search for books and send the books to App component by selecting the value in dropdown. Each book in BookSearch component will initially be assigned a shelf property of "none".When a user selects a shelf value from Booksearch,the book will automatically be added to the App component.So,when I navigate back to the App component from BookSearch,I should be able to see the book in the assigned shelf.So,for this case,I am using the updateSearch function. The books are displayed through the Book component which has the dropdown value.

pranami
  • 1,381
  • 5
  • 22
  • 43
  • The way you are passing callback from parent to child seems correct but the only problem being you are actually mutating the state in the function using this.state.books.push(booklist), which is not allowed (good way) – sahil gupta Nov 08 '17 at 09:10
  • @sahilgupta Is there any better way to add the new elements to the array without using push.I mean can you please suggest any better way to do the same. – pranami Nov 08 '17 at 09:15
  • yes please have a look at my answer, basically you need to create a new array instead of directly mutating the state. – sahil gupta Nov 08 '17 at 09:16
  • sure,let me try it out that way. – pranami Nov 08 '17 at 09:18
  • @pranami your code has a lot of irrelevant information, making it hard to work out what is going on. Your problem is simple to solve, but it is hard to work out your actual problem because you have so much irrelevant code. If you strip your code to a minimal version, and clearly illustrate your problem I will show you how to solve it quickly and easily. – Alex Nov 08 '17 at 10:09
  • @Alex actually I have pasted my whole code here for each component since it was difficult to put to put in words the exact issue.So, I was just trying to give an idea of what my components look like. For now, the other parts are working fine,only I am not able to update the parent state.So,if you want me to delete the components that are not using the state for my issue,that I can do.Apart from that,I am not sure how to strip more of the code. – pranami Nov 08 '17 at 10:17
  • @pranami Try going through every line of code and removing it - does your application still run, and does the problem still occur? If the answer to both of these is yes then you do not need that line in your SO question. If you follow this principle you will find that you can remove many of the lines of code in your example, and you may also find what the problem is yourself by doing this exercise. – Alex Nov 08 '17 at 10:21
  • @Alex, okay.I will proceed that way and check if it solves the issue. – pranami Nov 08 '17 at 10:24
  • @pranami once you have removed as much code as possible, if you have not managed to solve your problem update this question with the minified code and notify me and I will help you – Alex Nov 08 '17 at 10:25
  • sure @Alex, I will let you know if it doesn't solve the issue .Thanks :) – pranami Nov 08 '17 at 10:27

2 Answers2

1

If i understood this correctly, you are mutating the state in the function updateState.

What you should be doing instead is,

const updateState = (booksList) => {
   const books = [ ...this.state.books, ...booklist ];
   this.setState({ books });
}
sahil gupta
  • 2,339
  • 12
  • 14
  • I tried this,but its not giving the output as expected.I guess I am placing it the wrong way. Actually I want to run this method on the event click of a select dropdown but the onChange of the select already has another function running which will also update the state of the parent from another component.And for the component that I mentioned above, it has to update the parent state the way as shown.So for this scenario,is this approach wrong? Do I need to pass this updateSeach( ) function to the onChange event? – pranami Nov 08 '17 at 09:50
  • Shall I edit my question with the full code and show all the components? – pranami Nov 08 '17 at 09:51
  • yes that would be really helpful to understand the problem you are facing – sahil gupta Nov 08 '17 at 09:52
  • also rather than populating this question please create a new question for the above problem – sahil gupta Nov 08 '17 at 09:53
0

In parent component constructor add the following line:

this.updateState = this.updateState.bind(this)

More at https://reactjs.org/docs/handling-events.html

cosmin
  • 11
  • 1
  • Thanks for the answer, but I am still getting the errors.I mean my updateSearch( ) method is still not working as expected. – pranami Nov 08 '17 at 09:19