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'));