I am trying to build pagination into a simple react app that gets and displays a list of items. It seems like it's working properly when I log the state to the console because it's logging something different any time I click a new page. What's strange is anytime I go from page 2 to 3 or 1 to 3 (or vice versa) it will re-render as expected, but anytime I go between page 1 and 2, no dice. You can ignore the search bar for now, which likewise isn't working (I'm crossing my fingers that it's the same rendering issue) but one thing at a time. 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: []
};
// bind function in constructor instead of render (https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md)
//console.log(this.state.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={i} />
)}
</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:
Don't use index as a key
Changing
<BookCard book={item} key={i} />
to
<BookCard book={item} key={item.title} />
fixes the issue
But you should use an id for that