Reputation: 73
In my program, I have a variety of cards that are put on screen with a picture of a character and information about said character. I am pulling all of this information from an API, and am tasked with doing a client side pagination to display only a few of the cards on screen at a time.
Here is my code:
genCard = () => {
const { isLoaded, items, currentPage, totalNumberPages, recordsPerPage } = this.state;
if (isLoaded) {
let returnCard = [];
let i;
for(i = currentPage; i < recordsPerPage; i++) {
returnCard.push(<Card key={items[i].id} cardName={items[i].name} imgSrc={items[i].image} birthYear={items[i].birth_year}/>);
}
return returnCard;
}
return null;
};
handlePageClick = (data) => {
let selected = data.selected;
let offset = Math.ceil(selected * this.props.perPage);
this.setState({
})
};
As you can see, I'm using a for loop to display only 10 items (cards) on the screen at a time. What I'm trying to do is when you click on another page, I want it to re-render and display the other cards on screen.
So, how can I make this happen? How can I set the state to the page that you click on, so that it renders the correct cards onto the screen?
Thanks in advance for your help. Hopefully that made sense.
Upvotes: 1
Views: 2694
Reputation: 290
UPDATE: Adding code snippet here in addition to JSFiddle link below.
function Card(props) {
return <div>I'm card {props.id}</div>
}
class Cards extends React.Component {
constructor(props) {
super(props)
this.state = {
isLoaded: false,
items: [1,2,3,4,5,6,7,8,9,10, 11, 12, 13, 14, 15],
currentPage: 1,
recordsPerPage: 5,
itemStartIndexOnPage: 0,
tempCount: 0
}
}
getCards = () => {
const {items, itemStartIndexOnPage, recordsPerPage} = this.state;
const itemsForThisPage = items.slice(itemStartIndexOnPage, itemStartIndexOnPage + recordsPerPage);
let cards = itemsForThisPage.map((item, i) => <Card key={item} id={item} {...item} />)
return cards;
}
handlePageClick = (data) => {
let selected = data;
let offset = Math.ceil((selected - 1) * this.state.recordsPerPage);
this.setState({
currentPage: selected,
itemStartIndexOnPage: offset
})
}
render() {
return (
<div>
Page No: {this.state.currentPage}
{this.getCards()}
<button onClick={() => this.handlePageClick(1)}>Page 1</button>
<button onClick={() => this.handlePageClick(2)}>Page 2</button>
<button onClick={() => this.handlePageClick(3)}>Page 3</button>
</div>
)
}
}
ReactDOM.render(<Cards />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
I've wrote this snippet if that's what you are looking for.
setState
re-renders component tree. So call handlePageClick
on clicking pagination buttons and you can call getCards()
within render()
function which updates the cards in the component.
If you need to get subset of items from API end point on page-click you can make handlePageClick
asynchronous and call setState
after await
or in then
. If you are getting all the data at once, you can do that in componentDidMount
and store in state.
Upvotes: 1
Reputation: 12796
Well, let's be honest, you seem to be able to work with react already enough as you can access the state, and you are rendering some items as children, so what is stopping you currently from defining an onClick
handler that will call a class defined function (either with class properties you seemingly use) or by binding the method in the constructor?
Is it just the fact that you are missing a pager, so you can actually delegate the events?
I think Sai Sandeep Vaddis answer gives you already some information on how to deal with paged data, the only thing I want to add, is how you could structure over several components in order to keep it reusable throughout your application.
Now, keep in mind, I don't have a clue about your grander design, and the code you have shown (except for your loop which doesn't really start correct) seems to be working
const { Component } = React;
// overly basic pager
const Pager = ({ page, pageSize, itemCount, onPageChange }) => {
const totalPages = Math.ceil( itemCount / pageSize );
const children = [];
for (let i = 0; i < totalPages; i++) {
children.push( <span key={ i } onClick={ () => onPageChange(i) } className={ classNames( 'page-item', { selected: i === page } ) }>{ i + 1 }</span> );
}
return <div className="pager">{ children }</div>;
};
// a component to keep a local state (current page state, and all the items it holds)
class PagedList extends Component {
constructor() {
super();
this.state = {
page: 0
};
this.changePage = this.changePage.bind( this );
}
changePage( targetPage ) {
this.setState( { page: targetPage } );
// this could theoretically inform any parent that the page has changed
// in case this should be tracked from the parent (in which case having
// the page as state doesn't really make sense anymore, it was just a
// quick way to enable paging for already retrieved data
}
render() {
const { page = 0 } = this.state;
// some default settings on this.props (can be done with getDerivedProps or getDefaultProps as well)
const { items, pageSize = 10, component = props => JSON.stringify(props) } = this.props;
// no items yet, wait for the next render I suppose
if (!items) {
return <div>No items available</div>;
}
// render pager / current page items / pager
return <div>
<Pager page={ page } pageSize={ pageSize } itemCount={ items.length } onPageChange={ this.changePage } />
{ items.slice( page * pageSize, (page * pageSize) + pageSize ).map( item => React.createElement( component, {...item, key: item.id } ) ) }
<Pager page={ page } pageSize={ pageSize } itemCount={ items.length } onPageChange={ this.changePage } />
</div>
}
}
// very basic component displaying the name of the breed
const BreedItem = ({ name }) => <div>{ name }</div>;
// component that will load a random image when originally loaded
// could probably use a flag that says if it's still mounted before updating the state
// but, it's simply good to be aware of it :)
class BreedCard extends Component {
constructor() {
super();
this.state = {
};
}
componentDidMount() {
const { name } = this.props;
fetch( `https://dog.ceo/api/breed/${name}/images/random` )
.then( response => response.json() )
.then( data => this.setState( { image: data.message } ) );
}
render() {
const { name } = this.props;
const { image } = this.state;
return <div className="card">
<BreedItem name={ name } />
{ image ? <img src={ image } alt={ name } /> : <span>loading image</span> }
</div>;
}
}
// gets the items from the api (all at once to be fair)
// and then renders them in a PagedList
// doesn't really have a clue about what page it in, just decides on
// - pageSize
// - data
// - component to render
class BreedList extends Component {
constructor() {
super();
this.state = {
items: []
};
}
componentDidMount() {
fetch( 'https://dog.ceo/api/breeds/list/all' )
.then( response => response.json() )
.then( data => this.setState( { items: Object.keys( data.message ).map( key => ({ id: key, name: key }) ) } ) );
}
render() {
const { items } = this.state;
return <PagedList pageSize={10} items={items} component={ BreedCard } />;
}
}
// lets get cracking then :)
const container = document.querySelector('#container');
ReactDOM.render( <BreedList />, container );
.page-item {
display: inline-block;
padding: 5px;
cursor: pointer;
border-radius: 5px;
}
.page-item:hover {
background: silver;
}
.selected {
background-color: blue;
color: white;
}
.pager {
border: solid black 1px;
display: flex;
flex-direction: row;
font-size: .8em;
}
.card {
display: inline-flex;
flex-direction: column;
align-items: center;
border: solid #a0a0a0 1px;
margin: 3px;
padding: 5px;
}
.card > img {
max-width: 320px;
max-height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script id="classnames" src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
Upvotes: 0
Reputation: 121
You can add the following in handlePageClick
this.setState({ currentPage: selected })
and change the genCard as below
const startIndex = currentPage * recordsPerPage
for(i = startIndex i < startIndex + recordsPerPage; i++) {
returnCard.push(<Card key={items[i].id} cardName={items[i].name}
imgSrc={items[i].image} birthYear={items[i].birth_year}/>);
}
Upvotes: 0