Reputation: 1995
I'm stucked into a newbie problem for reactJS(I've started to learn last week). I'm developing a single page application that renders a search result from the Google Book API, and everything looks great, I can fetch the data from the api (actually right now im using AJAX).
None what I've tried in the last 8 hours seens to work to update the state in my react-paginate component. Probably the problem is in my handlePageClick function, but the documentation of this module is a little bit odd plus my unfamiliarity with react are making things harder.
So here's my app.js
import React from 'react';
import ReactDOM from 'react-dom';
import { compose } from 'redux';
import {
cloneDeep, findIndex, orderBy, keys, values, transforms
} from 'lodash';
import Bookshelf from './bookshelf.js';
import ReactPaginate from 'react-paginate';
import Header from './layout/header.js';
import Footer from './layout/Footer.js';
import Books from './layout/Books.js';
// import {Paginator, paginate } from './helpers';
const app = document.getElementById('app');
export default class Main extends React.Component{
constructor(props){
super(props);
this.state = {
items:[],
offset:0
};
this.localSubmit = this.localSubmit.bind(this);
this.onPerPage = this.onPerPage.bind(this);
this.handlePageClick = this.handlePageClick.bind(this);
}
localSubmit(search) {
this.setState({items: []});
var component = this;
$.get("https://www.googleapis.com/books/v1/volumes?q=intitle:" + encodeURIComponent(search) + "&printType=books&orderBy=newest&maxResults=39", function (data) {
component.setState(data);
Bookshelf();
$(".front").css("background", "url(../img/no_book_cover.jpg)");
for (var i = 0; i < component.state.items.length; i++) {
if (component.state.items[i].volumeInfo.imageLinks != null) {
$("#book-" + component.state.items[i].id).find(".front").css("background", "url("+ component.state.items[i].volumeInfo.imageLinks.thumbnail +")");
}
}
$(".front").css("background-size", "100% 100%");
$(".front").css("border", "2px solid #eee");
$(".front").css("background-size", "100% 100%");
});
}
handlePageClick(component){
console.log(component);
let selected = component.selected;
let offset = Math.ceil(selected * this.props.perPage);
console.log("the offset is:"+offset)
this.setState({offset: offset}, () => {
// this.localsubmit
});
}
onSelect(page) {
const pages = Math.ceil(
this.state.items.length / 9
);
console.log(pages);
this.setState({
pagination: {
perPage:this.state.pagination,
page: Math.min(Math.max(page, 1), pages)
}
});
}
onPerPage(value) {
this.setState({
pagination: {
page:this.state.pagination,
perPage: parseInt(value, 10)
}
});
}
render () {
const pages = Math.ceil(
this.state.items.length / 9
);
console.log("length:"+this.state.items.length);
console.log(pages);
var books = [];
var content;
books = this.state.items.map(function(book) {
return <Books key={book.id} item={book.volumeInfo} identifier={book.id} />;
});
if (books.length > 0) {
content = books;
} else {
content = <div className="search-icon" ><span className="glyphicon glyphicon-search"></span></div>
}
return (
<div>
<Header localSubmit={this.localSubmit}/>
<div className="main">
<div id="bookshelf" className="bookshelf">
{content}
</div>
<ReactPaginate previousLabel={"previous"}
nextLabel={"next"}
breakLabel={<a href="">...</a>}
breakClassName={"break-me"}
pageCount={this.state.pageCount}
marginPagesDisplayed={2}
pageRangeDisplayed={5}
onPageChange={this.handlePageClick}
containerClassName={"pagination"}
subContainerClassName={"pages pagination"}
activeClassName={"active"} />
</div>
<Footer />
</div>
);
}
}
ReactDOM.render(<Main perPage={10}/>, document.getElementById("app"));
And this is another relevant component, the books class
import React from "react";
import ReactDOM from 'react-dom';
var Books = React.createClass({
getInitialState : function () {
return ({});
},
componentDidMount : function () {
if (this.props.item != null) {
this.setState(this.props.item);
}
},
render : function () {
var authors = "";
console.log(this.props.item);
if (this.state.authors != null) {
for (var i = 0; i < this.state.authors.length; i++) {
if (i > 1) {
authors = ", " + this.state.authors[i];
} else {
authors = this.state.authors[i];
}
}
}
var descrip = "...";
if (this.state.description != null) {
descrip = this.state.description.substring(0, 180) + "...";
}
var id = "";
if (this.props.identifier != null) {
id = "book-" + this.props.identifier;
}
return (
<figure>
<div className="book" id={id}></div>
<div className="buttons"><a href={this.state.previewLink} target="_blank">Preview</a><a href="#">Details</a></div>
<figcaption><h2>{this.state.title}<span>{authors}</span></h2></figcaption>
<div className="details">
<ul>
<li>{descrip}</li>
<li>{this.state.publishedDate}</li>
<li>{this.state.publisher}</li>
<li>{this.state.pageCount} pages</li>
</ul>
</div>
</figure>
);
}
});
export default Books;
I've already tried another modules for paginate in react without success, and this one looks the easier, but there's something missing, could you guys help me?
Error:
When I click in the next page i got this Cannot read property 'perPage' of undefined
and in page 1
the whole content are being displayed, the react-paginate filtering actually it's not working.
Edit 2:
I've uncommented the line of this.handlePageClick = this.handlePageClick.bind(this);
inside the constructor but still getting no filter, and now i don't get any errors, this is driving me crazy =(
Thanks in advance!
Upvotes: 2
Views: 6658
Reputation: 4163
Error: When I click in the next page i got this Cannot read property 'perPage' of undefined and in page 1 the whole content are being displayed, the react-paginate filtering actually it's not working.
You need to bind the handlePageClick
to this
in the constructor (the line is commented), like you did with onPerPage
and another method.
EDIT 2 after your update Ok looks better now. However your code is really messy, I can clearly see you going around, changing things and trying. :) Do yourself a favor and remove all that jQuery code all around, specially in the Ajax handler. If you want a different HTML, simply update your components.
To go straight to your question, and after checking the react-paginate
docs, it only renders a paginator for you, but you still have to render
only the number of items to be shown. In your code you're mapping the full this.state.items
array and that will render every book every time.
On top of that, I see you're creating new state pieces in your onPerPage
and onSelect
methods which you didn't initialize in your constructor
.
The only state your app needs is:
items
returned from the API callconst PAGE_SIZE = 9
. You may later turn that into a prop
so your component's default page size is configurable from the outside.Given the 2 first keys, declare your initial state in your constructor
like:
this.state = {
items: [],
currentPage: 1
}
Then, and here's the important part, extract your items
to <Book />
mapping logic to a function, that takes into account the PAGE_SIZE
and currentPage
to select only the relevant items from the array, something like:
renderBooks(){
var startIndex = (this.state.currentPage-1)*PAGE_SIZE
var endIndex = startIndex + PAGE_SIZE
return this.state.items.slice(startIndex, endIndex).map(item =>
<Book {...your_props} />
}
Finally, in your render method simply call that renderBooks()
function everytime you want to render books (i.e., everytime this.state.items.length > 0
).
On your events handlers for the Paginate component, simply set the appropiate value for currentPage
and it will do the trick. You don't need to all the Math stuff, as long as there's no Next 0.25 page button in your paginator, you'll be working with integers all the time. :)
Hope it helps!
P.S: what's that Bookshelf()
call in your AJAX handler? You should only setState({ items })
there!! And take into account the new state in your render
methods in all your components.
Upvotes: 3