Home > Back-end >  React app not re-rendering child component on setState
React app not re-rendering child component on setState

Time:10-23

I'm trying to make the search function work on this simple react app. I can see that it is getting the new data properly when I log it to the console, but it doesn't seem like the child component that builds the "page" to display the data with pagination is ever being called after the initial rendering. Codepen & code below:

https://codepen.io/eacres/pen/LYmwmmZ

const propTypes = {
    books: React.PropTypes.array.isRequired,
    onChangePage: React.PropTypes.func.isRequired,
    initialPage: React.PropTypes.number    
}

const defaultProps = {
    initialPage: 1
}

class Pagination extends React.Component {
    constructor(props) {
        super(props);
        this.state = { pager: {} };
    }

    componentWillMount() {
        // set page if books array isn't empty
        if (this.props.books && this.props.books.length) {
            this.setPage(this.props.initialPage);
        }
    }

    componentDidUpdate(prevProps, prevState) {
        // reset page if books array has changed
        if (this.props.books !== prevProps.books) {
            this.setPage(this.props.initialPage);
        }
    }

    setPage(page) {
        var books = this.props.books;
        var pager = this.state.pager;
        
        if (page < 1 || page > pager.totalPages) {
            return;
        }

        // get new pager object for specified page
        pager = this.getPager(books.length, page);

        // get new page of books from books array
      
        var pageofBooks = books.slice(pager.startIndex, pager.endIndex   1);
      
        // update state
        this.setState({ pager: pager });

        // call change page function in parent component
        this.props.onChangePage(pageofBooks);
    }

    getPager(totalbooks, currentPage, pageSize) {
        // default to first page
        currentPage = currentPage || 1;

        // default page size is 8
        pageSize = pageSize || 8;

        // calculate total pages
        var totalPages = Math.ceil(totalbooks / pageSize);

        var startPage, endPage;
        if (totalPages <= 10) {
            // less than 10 total pages so show all
            startPage = 1;
            endPage = totalPages;
        } else {
            // more than 10 total pages so calculate start and end pages
            if (currentPage <= 6) {
                startPage = 1;
                endPage = 10;
            } else if (currentPage   4 >= totalPages) {
                startPage = totalPages - 9;
                endPage = totalPages;
            } else {
                startPage = currentPage - 5;
                endPage = currentPage   4;
            }
        }

        // calculate start and end item indexes
        var startIndex = (currentPage - 1) * pageSize;
        var endIndex = Math.min(startIndex   pageSize - 1, totalbooks - 1);

        // create an array of pages to ng-repeat in the pager control
        var pages = [...Array((endPage   1) - startPage).keys()].map(i => startPage   i);
        
        // return object with all pager properties required by the view
        return {
            totalbooks: totalbooks,
            currentPage: currentPage,
            pageSize: pageSize,
            totalPages: totalPages,
            startPage: startPage,
            endPage: endPage,
            startIndex: startIndex,
            endIndex: endIndex,
            pages: pages
        };
    }

    render() {
        var pager = this.state.pager;

        if (!pager.pages || pager.pages.length <= 1) {
            // don't display pager if there is only 1 page
            return null;
        }

        return (
            <ul className="pagination">
                <li className={pager.currentPage === 1 ? 'disabled' : ''}>
                    <a onClick={() => this.setPage(1)}>First</a>
                </li>
                <li className={pager.currentPage === 1 ? 'disabled' : ''}>
                    <a onClick={() => this.setPage(pager.currentPage - 1)}>Previous</a>
                </li>
                {pager.pages.map((page, index) =>
                    <li key={index} className={pager.currentPage === page ? 'active' : ''}>
                        <a onClick={() => this.setPage(page)}>{page}</a>
                    </li>
                )}
                <li className={pager.currentPage === pager.totalPages ? 'disabled' : ''}>
                    <a onClick={() => this.setPage(pager.currentPage   1)}>Next</a>
                </li>
                <li className={pager.currentPage === pager.totalPages ? 'disabled' : ''}>
                    <a onClick={() => this.setPage(pager.totalPages)}>Last</a>
                </li>
            </ul>
        );
    }
}

Pagination.propTypes = propTypes;
Pagination.defaultProps = defaultProps;


/* App Component 
-------------------------------------------------*/

class App extends React.Component {
    constructor() {
        super();

        // an example array of books to be paged
        axios.get(`https://goodreads-server-express--dotdash.repl.co/search/name`)
      .then(response => {
        this.setState({bookList: response.data.list}) ;
      })
      .catch(error => {
        // edge case
        // alert("Yikes! Looks like we don't have anything for that search. Please edit your search and try again.");
        console.log(error);
      });
      
        this.state = {
            bookList: [],
            pageofBooks: []
        };

      this.onChangePage = this.onChangePage.bind(this);
      this.handleChange = this.handleChange.bind(this);
      this.handleSubmit = this.handleSubmit.bind(this);
    }

    onChangePage(pageofBooks) {
        // update state with new page of books
      
        this.setState({ pageofBooks: pageofBooks }, () => {
          //console.log(this.state.pageofBooks)
        });
    }
  
    handleChange (e) {
      e.preventDefault();
      this.setState({searchString: e.target.value})
    }

    handleSubmit (e) {
      e.preventDefault();
      this.setState({ bookList : [] });

      // edge case
      if (!this.state.searchString) {
        alert('Oops! Please enter your search in the box below.')
      } else {
        axios.get(`https://goodreads-server-express--dotdash.repl.co/search/${this.state.searchString}`)
        .then(response => {
          this.setState({ bookList: response.data.list });
        })
        .catch(error => {
          // edge case
          alert("Yikes! Looks like we don't have anything for that search. Please edit your search and try again.");
          console.log(error);
        });
      } 
    }

    render() {
        return (
            <div>
                <div className="container">
                    <div className="text-center">
                      <form className="search-bar">
                        <label htmlFor="search">Find me a book</label>
                        <input id="search" onChange={this.handleChange} />
                        <button onClick={this.handleSubmit}>Search</button>
                      </form>
                      <div className="search-results">
                        {this.state.pageofBooks.map( (item, i) =>
                            <BookCard book={item} key={item.title} />
                        )}
                      </div>
                        <Pagination books={this.state.bookList} onChangePage={this.onChangePage} />
                    </div>
                </div>
                <hr />
            </div>
        );
    }
}

class BookCard extends React.Component {
  constructor(props) {
    super(props);
    
    this.state = {
      author: props.book.authorName,
      title: props.book.title,
      image: props.book.imageUrl
    }
  }

  render() {
    return (
      <div className="book-card">
        <div className="image__container">
          <img src={this.state.image} />
        </div>
        <div className="book-card__header">
          <h3>{this.state.author}</h3>
          <h2>{this.state.title.length > 40 ? this.state.title.slice(0, 40)   '...' : this.state.title}</h2>
        </div>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('app'));

Thanks in advance!

CodePudding user response:

Props shouldn't be stored in the state

Notice that constructor is only called once so the state never had a chance to update

class BookCard extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div className="book-card">
        <div className="image__container">
          <img src={this.props.imageUrl} />
        </div>
        <div className="book-card__header">
          <h3>{this.props.authorName}</h3>
          <h2>{this.props.title.length > 40 ? this.props.title.slice(0, 40)   '...' : this.props.title}</h2>
        </div>
      </div>
    );
  }
}

CodePudding user response:

The issue might be

var pager = this.stata.pager;

Replaced it by

var pager = this.getPager(books.length, page);

Hope this is it

const propTypes = {
    books: React.PropTypes.array.isRequired,
    onChangePage: React.PropTypes.func.isRequired,
    initialPage: React.PropTypes.number    
}

const defaultProps = {
    initialPage: 1
}

class Pagination extends React.Component {
    constructor(props) {
        super(props);
        this.state = { pager: {} };
    }


    componentDidUpdate(prevProps, prevState) {
        // reset page if books array has changed
        console.log('1', this.props.books?.[0], prevProps.books?.[0])
       
        if (this.props.books !== prevProps.books) {
            console.log('this.props.books 2', this.props.books);
            this.setPage(this.props.initialPage);
        }
    }

    setPage(page) {
        var books = this.props.books;
        var pager = this.getPager(books.length, page);
        console.log('pager', pager)
        if (page < 1 || page > pager.totalPages) {
            return;
        }

        // get new pager object for specified page


        // get new page of books from books array
      
        var pageofBooks = books.slice(pager.startIndex, pager.endIndex   1);
      
        // update state
        this.setState({ pager: pager });

        // call change page function in parent component
        console.log('pageofBooks', pageofBooks)
        console.log('books', books)
        this.props.onChangePage(pageofBooks);
    }

    getPager(totalbooks, currentPage, pageSize) {
        // default to first page
        currentPage = currentPage || 1;

        // default page size is 8
        pageSize = pageSize || 8;

        // calculate total pages
        var totalPages = Math.ceil(totalbooks / pageSize);

        var startPage, endPage;
        if (totalPages <= 10) {
            // less than 10 total pages so show all
            startPage = 1;
            endPage = totalPages;
        } else {
            // more than 10 total pages so calculate start and end pages
            if (currentPage <= 6) {
                startPage = 1;
                endPage = 10;
            } else if (currentPage   4 >= totalPages) {
                startPage = totalPages - 9;
                endPage = totalPages;
            } else {
                startPage = currentPage - 5;
                endPage = currentPage   4;
            }
        }

        // calculate start and end item indexes
        var startIndex = (currentPage - 1) * pageSize;
        var endIndex = Math.min(startIndex   pageSize - 1, totalbooks - 1);

        // create an array of pages to ng-repeat in the pager control
        var pages = [...Array((endPage   1) - startPage).keys()].map(i => startPage   i);
        
        // return object with all pager properties required by the view
        return {
            totalbooks: totalbooks,
            currentPage: currentPage,
            pageSize: pageSize,
            totalPages: totalPages,
            startPage: startPage,
            endPage: endPage,
            startIndex: startIndex,
            endIndex: endIndex,
            pages: pages
        };
    }

    render() {
        var pager = this.state.pager;

        if (!pager.pages || pager.pages.length <= 1) {
            // don't display pager if there is only 1 page
            return null;
        }

        return (
            <ul className="pagination">
                <li className={pager.currentPage === 1 ? 'disabled' : ''}>
                    <a onClick={() => this.setPage(1)}>First</a>
                </li>
                <li className={pager.currentPage === 1 ? 'disabled' : ''}>
                    <a onClick={() => this.setPage(pager.currentPage - 1)}>Previous</a>
                </li>
                {pager.pages.map((page, index) =>
                    <li key={index} className={pager.currentPage === page ? 'active' : ''}>
                        <a onClick={() => this.setPage(page)}>{page}</a>
                    </li>
                )}
                <li className={pager.currentPage === pager.totalPages ? 'disabled' : ''}>
                    <a onClick={() => this.setPage(pager.currentPage   1)}>Next</a>
                </li>
                <li className={pager.currentPage === pager.totalPages ? 'disabled' : ''}>
                    <a onClick={() => this.setPage(pager.totalPages)}>Last</a>
                </li>
            </ul>
        );
    }
}

Pagination.propTypes = propTypes;
Pagination.defaultProps = defaultProps;


/* App Component 
-------------------------------------------------*/

class App extends React.Component {
    constructor() {
        super();

        // an example array of books to be paged
        axios.get(`https://goodreads-server-express--dotdash.repl.co/search/name`)
      .then(response => {
        this.setState({bookList: response.data.list, page: 1}) ;

      })
      .catch(error => {
        // edge case
        // alert("Yikes! Looks like we don't have anything for that search. Please edit your search and try again.");
        console.log(error);
      });
      
        this.state = {
            bookList: [],
            pageofBooks: []
        };

      this.onChangePage = this.onChangePage.bind(this);
      this.handleChange = this.handleChange.bind(this);
      this.handleSubmit = this.handleSubmit.bind(this);
    }

    onChangePage(pageofBooks) {
        // update state with new page of books
      
        this.setState({ pageofBooks: pageofBooks }, () => {
          //console.log(this.state.pageofBooks)
        });
    }
  
    handleChange (e) {
      e.preventDefault();
      this.setState({searchString: e.target.value})
    }

    handleSubmit (e) {
      e.preventDefault();
      this.setState({ bookList : [] });

      // edge case
      if (!this.state.searchString) {
        alert('Oops! Please enter your search in the box below.')
      } else {
        axios.get(`https://goodreads-server-express--dotdash.repl.co/search/${this.state.searchString}`)
        .then(response => {
          console.log('response.data.list', response.data.list)
          this.setState({ bookList: response.data.list });
        
        })
        .catch(error => {
          // edge case
          alert("Yikes! Looks like we don't have anything for that search. Please edit your search and try again.");
          console.log(error);
        });
      } 
    }
   

    render() {
       console.log('this.state.bookList', this.state.bookList)
        return (
            <div>
                <div className="container">
                    <div className="text-center">
                      <form className="search-bar">
                        <label htmlFor="search">Find me a book</label>
                        <input id="search" onChange={this.handleChange} />
                        <button onClick={this.handleSubmit}>Search</button>
                      </form>
                      <div className="search-results">
                        {this.state.pageofBooks.map( (item, i) =>
                            <BookCard book={item} key={item.title} />
                        )}
                      </div>
                        <Pagination books={this.state.bookList} onChangePage={this.onChangePage} />
                    </div>
                </div>
                <hr />
            </div>
        );
    }
}

class BookCard extends React.Component {
  constructor(props) {
    super(props);
    
    this.state = {
      author: props.book.authorName,
      title: props.book.title,
      image: props.book.imageUrl
    }
  }

  render() {
    return (
      <div className="book-card">
        <div className="image__container">
          <img src={this.state.image} />
        </div>
        <div className="book-card__header">
          <h3>{this.state.author}</h3>
          <h2>{this.state.title.length > 40 ? this.state.title.slice(0, 40)   '...' : this.state.title}</h2>
        </div>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('app'));
  • Related