I'm using the react-bootstrap to paginate the result. It is rendering the div #content, but it is not showing nothing more. It is showing only a empty div with width, height and background color as I configured on the CSS file. I would like to display the pagination showing one house per page. The result of data from JSON is catched successfully. How can I solve the pagination issue? Thanks!
import React, { Component } from 'react'
import axios from 'axios'
import { Pagination } from 'react-bootstrap'
const URL_HOUSES = 'http://localhost:3001/houses';
class Casas extends Component {
constructor(props) {
super(props)
this.state = {
houses: []
}
this.handlePageChange = this.handlePageChange.bind(this)
}
getNumPages(currentPage) {
{ this.handlePageChange }
this.setState({
per_page: this.props.results ,
currentPage: currentPage + 1 ,
previousPage: currentPage - 1
});
}
handlePageChange(page, evt) {
const currentPage = this.state.currentPage || 1;
const numPages = this.getNumPages();
const pageLinks = [];
if (currentPage > 1) {
if (currentPage > 2) {
pageLinks.push(1);
pageLinks.push(' ');
}
pageLinks.push(currentPage - 1);
pageLinks.push(' ');
}
for (let i = 1; i <= numPages; i++) {
const page = i;
pageLinks.push(page);
}
if (currentPage < numPages) {
pageLinks.push(' ');
pageLinks.push(currentPage + 1);
if (currentPage < numPages - 1) {
pageLinks.push(' ');
pageLinks.push(numPages);
}
}
this.setState({ currentPage: currentPage + 1 } );
this.setState({ previousPage: currentPage - 1 } );
}
componentDidMount() {
axios.get(URL_HOUSES)
.then(res => {
this.setState({ houses: res.data })
})
}
render() {
const per_page = "1";
const paginationData = this.state.houses
let numPages = Math.ceil(paginationData.length / per_page);
if (paginationData.length % per_page > 0) {
numPages++;
}
return (
<div>
{this.state.houses.map(item =>
<div>
<h2>{item.name}</h2>
<p>{item.description}</p>
<ul>
{
item.photos.map(photo => <li>{photo}</li>)
}
</ul>
</div>
)}
<Pagination id="content" className="users-pagination pull-right"
bsSize="medium"
first last next prev boundaryLinks items={numPages}
activePage={ this.state.currentPage } onSelect={ this.handlePageChange
} />
</div>
)
}
}
export default Houses;
import React, { Component } from "react";
import ReactDOM from "react-dom";
import Pagination from "react-js-pagination";
require("bootstrap/less/bootstrap.less");
class App extends Component {
constructor(props) {
super(props);
this.state = {
activePage: 1,
itemPerPage: 3,
productList: [],
duplicateProductList: []
};
}
componentDidMount() {
let d = '';
$.get("YOUR API", function (data) {
d = data;
this.setState({
projectList: d,
duplicateProductList: d
});
}.bind(this));
}
handlePageChange(pageNumber) {
this.setState({ activePage: pageNumber });
}
render() {
const { projectList, activePage, itemPerPage } = this.state;
const indexOfLastTodo = activePage * itemPerPage;
const indexOfFirstTodo = indexOfLastTodo - itemPerPage;
const renderedProjects = projectList.slice(indexOfFirstTodo, indexOfLastTodo);
return (
<div>
<div>
YOUR LIST
</div>
<Pagination
activePage={this.state.activePage}
itemsCountPerPage={this.state.itemPerPage}
totalItemsCount={this.state.duplicateProductList.length}
pageRangeDisplayed={5}
onChange={this.handlePageChange.bind(this)}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
Can you follow this link https://www.npmjs.com/package/react-js-pagination
Related
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!
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'));
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>
);
}
}
I have this ReactJS App
See the app working
https://codepen.io/claudio-bitar/pen/VERORW
It returns a pagination with at maximum eight numbers per page.
Example:
I have ten numbers, so
Page one: 1, 2, 3, 4, 5, 6, 7, 8
page two: 9, 10
I would like to cover with '0' the blank numbers space until I reached 8 numbers. For example in the page two I would like to return:
9, 10, 0, 0, 0, 0, 0, 0
How can I solve it? Thank you
The App code:
class TodoApp extends React.Component {
constructor() {
super();
this.state = {
todos: {
"elements": ['1','2','3','4','5','6','7','8','9','10']
},
currentPage: 1,
todosPerPage: 8
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
this.setState({
currentPage: Number(event.target.id)
});
}
render() {
const { todos, currentPage, todosPerPage } = this.state;
// Logic for displaying current todos
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = todos.elements.slice(indexOfFirstTodo, indexOfLastTodo);
const renderTodos = currentTodos.map((todo, index) => {
return <li key={index}>{todo}</li>;
});
// Logic for displaying page numbers
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(todos.elements.length / todosPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<li
key={number}
id={number}
onClick={this.handleClick}
>
{number}
</li>
);
});
return (
<div>
<ul>
{renderTodos}
</ul>
<ul id="page-numbers">
{renderPageNumbers}
</ul>
</div>
);
}
}
ReactDOM.render(
<TodoApp />,
document.getElementById('app')
);
You could create a loop that will loop until currentTodos has todosPerPage elements in it and just push 0 until it does.
class TodoApp extends React.Component {
// ...
render() {
const { todos, currentPage, todosPerPage } = this.state;
// Logic for displaying current todos
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = todos.elements.slice(
indexOfFirstTodo,
indexOfLastTodo
);
while (currentTodos.length !== todosPerPage) {
currentTodos.push(0);
}
// ...
}
}
class TodoApp extends React.Component {
constructor() {
super();
this.state = {
todos: {
elements: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
},
currentPage: 1,
todosPerPage: 8
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
this.setState({
currentPage: Number(event.target.id)
});
}
render() {
const { todos, currentPage, todosPerPage } = this.state;
// Logic for displaying current todos
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = todos.elements.slice(
indexOfFirstTodo,
indexOfLastTodo
);
while (currentTodos.length !== todosPerPage) {
currentTodos.push(0);
}
const renderTodos = currentTodos.map((todo, index) => {
return <li key={index}>{todo}</li>;
});
// Logic for displaying page numbers
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(todos.elements.length / todosPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<li key={number} id={number} onClick={this.handleClick}>
{number}
</li>
);
});
return (
<div>
<ul>{renderTodos}</ul>
<ul id="page-numbers">{renderPageNumbers}</ul>
</div>
);
}
}
ReactDOM.render(<TodoApp />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
I have a ReactJS code below made for a SO user Piotr Berebecki. The code is working succefully It is a handle pagination returning items from an array. I want to do the same thing but returning data from JSON DB How can I do? How can I solve it? Thank you. Here is the app in the CodePen.
Here is My DB Json. I want only return the images (named as 'fotos'). It will replace the 'todos' in the array in the second code below
. Note: It must be made by Axios.
{
"interiores": [
{
"nome": "house 1",
"descricao": "This is the description of the house 1",
"fotos": [
"int_02", "int_02", "int_02", "int_02", "int_02"
]
}
]
}
Code:
import React, { Component } from 'react'
class Todo extends Component {
constructor() {
super();
this.state = {
todos: ['a','b','c','d','e','f','g','h','i','j','k'],
currentPage: 1,
todosPerPage: 3
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
this.setState({
currentPage: Number(event.target.id)
});
}
render() {
const { todos, currentPage, todosPerPage } = this.state;
// Logic for displaying todos
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = todos.slice(indexOfFirstTodo, indexOfLastTodo);
const renderTodos = currentTodos.map((todo, index) => {
return <li key={index}>{todo}</li>;
});
// Logic for displaying page numbers
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(todos.length / todosPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<li
key={number}
id={number}
onClick={this.handleClick}
>
{number}
</li>
);
});
return (
<div>
<ul>
{renderTodos}
</ul>
<ul id="page-numbers">
{renderPageNumbers}
</ul>
</div>
);
}
}
export default Todo;
It seems like you're asking how to handle the data once you've already made the call, this is how I think you should do it using componentDidMount I haven't tested it yet, but should give you a good starting point.
{
"interiores": [
{
"nome": "house 1",
"descricao": "This is the description of the house 1",
"fotos": [
"int_02", "int_02", "int_02", "int_02", "int_02"
]
}
]
}
import React, { Component } from 'react'
class Todo extends Component {
constructor() {
super();
this.state = {
todos: ['a','b','c','d','e','f','g','h','i','j','k'],
currentPage: 1,
todosPerPage: 3 ,
fotos: '',
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
this.setState({
currentPage: Number(event.target.id)
});
}
async componentDidMount() {
//make call to database and set the db data to your state.
const dbData = axios.get('http://yourapi.com/todods')
.then(function (response) {
this.setState({fotos: response.data.interiores[0].fotos})
})
.catch(function (error) {
console.log('error:', error);
});
}
render() {
const { todos, currentPage, todosPerPage } = this.state;
// Logic for displaying todos
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = todos.slice(indexOfFirstTodo, indexOfLastTodo);
const renderTodos = currentTodos.map((todo, index) => {
return <li key={index}>{todo}</li>;
});
// Logic for displaying page numbers
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(todos.length / todosPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<li
key={number}
id={number}
onClick={this.handleClick}
>
{number}
</li>
);
});
return (
<div>
<ul>
{this.state.fotos? this.state.fotos : 'nothing to display' }
</ul>
<ul id="page-numbers">
{renderPageNumbers}
</ul>
</div>
);
}
}
export default Todo;
My homepage is fetching data from the API, putting it to component's state and rendering elements based on this state.
When I navigate to a subpage and then I want to go to the homepage again the data is fetched again which causes unnecessary load. How to prevent that? How to ensure that when I click a link to the homepage or back button the data will load immediately?
import React, {Component} from 'react';
import axios from 'axios';
import _ from 'lodash';
import { Link } from 'react-router-dom';
import ContributorsTable from './ContributorsTable';
import LoadingScreen from './LoadingScreen';
export const API_KEY = 'api-key'
const org = `https://api.github.com/orgs/angular?${API_KEY}`;
const unorderedContributors = [];
let contributorsList = [];
const contributorPromises = [];
const contributorPropertiesPromises = [];
class GitHubLists extends Component {
constructor(props) {
super(props);
this.state = {
repos: [],
contributors: [],
isLoaded: false
};
}
componentDidMount() {
axios.get(org)
.then(res => {
let numberRepos = res.data.public_repos;
let pages = Math.ceil(numberRepos/100);
let tmpRepos = [...this.state.repos];
for(let page = 1; page <= pages; page++) {
axios.get(`https://api.github.com/orgs/angular/repos?page=${page}&per_page=100&${API_KEY}`)
.then(res => {
for(let i = 0; i < res.data.length; i++) {
tmpRepos.push(res.data[i]);
}
this.setState({repos: tmpRepos});
})
.then(() => {
this.state.repos.map(repo =>
contributorPromises.push(axios.get(`${repo.contributors_url}?per_page=100&${API_KEY}`)
.then(res => {
if(!res.headers.link) {
unorderedContributors.push(res.data);
}
else {
for(let page = 1; page <= 5; page++) {//5 pages because of github limitation - can be done by recursion checking if res.headers.link.includes('rel="next"')
contributorPromises.push(
axios.get(`${repo.contributors_url}?page=${page}&per_page=100&${API_KEY}`)
.then(res => unorderedContributors.push(res.data))
)
}
}
}))
);
Promise.all(contributorPromises).then(() => {
contributorsList = _.chain(unorderedContributors)
.flattenDeep()
.groupBy('id')
.map((group, id) => ({
id: parseInt(id, 10),
login: _.first(group).login,
contributions: _.sumBy(group, 'contributions'),
contributorFollowers: 0,
followers_url: _.first(group).followers_url,
contributorRepositories: 0,
repos_url: _.first(group).repos_url,
contributorGists: 0,
gists_url: _.first(group).gists_url,
avatar: _.first(group).avatar_url,
url: _.first(group).html_url
}))
.orderBy(['contributions'],['desc'])
.filter((item) => !isNaN(item.id))
.value();
this.setState({contributors: contributorsList})
})
.then(() => {
let tmpContributors = [...this.state.contributors];
tmpContributors.map(contributor => contributor.gists_url = (contributor.gists_url).slice(0, -10));
tmpContributors.map(contributor => {
return contributor.link =
<div>
<Link
to={{
pathname: `contributors/${contributor.login}`,
state: {
login: contributor.login,
id: contributor.id,
repos_url: contributor.repos_url,
avatar: contributor.avatar
}
}}
>
See profile
</Link>
</div>
});
const getContributorProperties = (propertyUrl, contributorProperty) => {
for (let i = 0; i < 10; i++) {
contributorPropertiesPromises.push(axios.get(`${tmpContributors[i][propertyUrl]}?per_page=100&${API_KEY}`)
.then(res => {
if(res.data.length > 100) {
tmpContributors[i][contributorProperty] = res.data.length;
}
else {
for(let page = 1; page <= 5; page++) {
axios.get(`${tmpContributors[i][propertyUrl]}?page=${page}&per_page=100&${API_KEY}`)
tmpContributors[i][contributorProperty] += res.data.length;
}
}
})
)
}
}
getContributorProperties('followers_url', 'contributorFollowers');
getContributorProperties('repos_url', 'contributorRepositories');
getContributorProperties('gists_url', 'contributorGists');
Promise.all(contributorPropertiesPromises)
.then(() => this.setState({contributors: tmpContributors, isLoaded: true}))
})
})
}
})
}
render() {
if(this.state.isLoaded) {
return <ContributorsTable data={this.state.contributors}/>;
}
else {
return <LoadingScreen />
}
}
}
export default GitHubLists;
Instead of keeping your state at the route level, keep it at the app level (or somewhere that persists across url changes / children mounting + unmounting like a redux store).
class App {
state = { data: null }
fetchData() {
callApi().then(data => this.setState({ data }))
}
render() {
return (
<Router>
<div>
<Route
path="/page"
component={props =>
<Page data={this.state.data} fetchData={this.fetchData} />
}
/>
</div>
</Router>
)
}
}
class Page {
componentDidMount() {
if (!this.props.data) this.props.fetchData()
}
render() { ... }
}
React docs have a spot on "lifting state". The same principle applies when using React Router: https://facebook.github.io/react/docs/lifting-state-up.html
I am creating a pagination component and it's have piece of prepare logic before render.
import React, {PropTypes, Component} from "react";
import PaginationItemComponent from "scripts/components/paginationItemComponent";
const MAX_ITEMS = 10;
const prepareShiftItems = (activeShift, pagesCount) => {
let toLeftCaption, toRightCaption, endPage;
const startPage = activeShift * MAX_ITEMS + 1;
const end = (activeShift + 1) * MAX_ITEMS;
if (end >= pagesCount) {
toRightCaption = null;
endPage = pagesCount;
} else {
endPage = end;
toRightCaption = "-->";
}
if (activeShift === 0) {
toLeftCaption = null;
} else {
toLeftCaption = "<--";
}
let activePages = [];
for (let i = startPage; i <= endPage; i++) {
activePages.push(i);
}
return {
toLeftCaption,
toRightCaption,
activePages
};
};
const getActiveShiftByPageNumber = (activePageNumber) => {
const div = (activePageNumber - 1) / MAX_ITEMS;
return Math.floor(div);
};
class PaginationComponent extends Component {
constructor(props) {
super(props);
this.state = {
activePageNumber: props.activePageNumber,
activeShift: getActiveShiftByPageNumber(props.activePageNumber)
};
}
componentWillReceiveProps(nextProps) {
this.setState({
activePageNumber: nextProps.activePageNumber,
activeShift: getActiveShiftByPageNumber(nextProps.activePageNumber)
});
}
setActivePage(activePageNumber) {
if (this.state.activePageNumber === activePageNumber) {
return;
}
const {onChange} = this.props;
if (onChange) {
onChange(activePageNumber);
}
this.setState({
activePageNumber
});
}
moveToRight() {
this.setState({
activeShift: this.state.activeShift + 1
});
}
moveToLeft() {
this.setState({
activeShift: this.state.activeShift - 1
});
}
render() {
if (this.props.pagesCount < 2) {
return null;
}
const {activePages, toLeftCaption, toRightCaption} =
prepareShiftItems(
this.state.activeShift,
this.props.pagesCount
);
return (
<ul className="pagination-component">
{
toLeftCaption &&
<li onClick={this.moveToLeft.bind(this)} className="to-left">
{toLeftCaption}
</li>
}
{
activePages.map((pageNumber) =>
<PaginationItemComponent
key={pageNumber}
pageNumber={pageNumber}
onClick={this.setActivePage.bind(this, pageNumber)}
isActive={pageNumber === this.state.activePageNumber}/>)
}
{
toRightCaption &&
<li onClick={this.moveToRight.bind(this)} className="to-right">
{toRightCaption}
</li>
}
</ul>
);
}
}
const {number, func} = PropTypes;
PaginationComponent.propTypes = {
pagesCount: number.isRequired,
activePageNumber: number.isRequired,
onChange: func
};
export default PaginationComponent;
My question is: do i need to call prepareShiftItems first time in constructor and then in componentWillReceiveProps or i can leave it as it is. I want to know the best practice for such cases. Thanks for answers.