React | wait until useEffect is finished - javascript

EDIT:
Hello guys,
my jsx looks like this
import { DataGrid, GridColDef, GridValueGetterParams } from '#mui/x-data-grid';
import React, { useState, useEffect } from 'react';
import { getNameOfJSDocTypedef, WatchDirectoryFlags } from '../../../node_modules/typescript/lib/typescript';
// ==============================|| MOB ||============================== //
let first_run = true;
var columns = [];
var rows = [];
const Mob = () => {
const [antDataColumns, setAntDataColumns] = useState([]);
const urlAntColumns = "QUERYURL";
const [antData, setAntData] = useState([]);
const urlAnt = 'QUERYURL';
useEffect(() => {
fetch(urlAntColumns)
.then((response) => {
return response.json();
})
.then((data) => {
setAntDataColumns(data);
});
}, []);
useEffect(() => {
fetch(urlAnt)
.then((response) => {
return response.json();
})
.then((data) => {
setAntData(data);
});
}, []);
if (antDataColumns.length > 0 && antData.length > 0 && first_run === true) {
columns.push({ field: 'id', headerName: 'ID', width: 70 })
for (let i = 6; i < 12; i++){
columns.push({field: antDataColumns[i]['column_name'], headerName: antDataColumns[i]['column_name'], width: 70});
}
for (let i = 0; i < 20; i++){
let row = {};
row["id"] = i+1;
for (let j = 1; j < columns.length; j++) {
row[columns[j]['field']] = antData[i][columns[j]['field']];
}
rows.push(row);
}
first_run = false;
}
console.log("---------------------------------------------------------------------------------------------------------")
console.log(columns)
console.log("---")
console.log(rows)
console.log("---------------------------------------------------------------------------------------------------------")
return (
<div style={{ height: 1000, width: '100%' }}>
<DataGrid
rows={rows}
columns={columns}
pageSize={10}
rowsPerPageOptions={[10]}
checkboxSelection
/>
</div>
)
};
export default Mob;
The table does not show up. But the data is loaded after a few seconds... I don't understand why shows nothing. Im using MATERIAL UI. Data Grid.
The 'columns' and 'rows' look like this
But I get a error and not output. Maybe because fetch is not finished when the website is rendering...
Thats the error

You can stop the component from rendering its content, based on a condition from your state.
const [antDataColumns, setAntDataColumns] = useState([]);
useEffect(() => {
...
}, []);
if (!antDataColumns.length) return null;
console.log("X")
Or you can create a second state : a loading state, that is true while the request is processed and false when it is finished.

Related

Setting state in react native freezes app

In a react native I am trying create a class component that loads a list of categories with a tag and an AWS S3 image attached.
If you look at this code the app freezes on the line:
this.setState({ categories });
But if I comment out that line the console logs that categories array just fine.
Please help any ideas why?
I have tried creating a functional component also but same this happens.
import React, { Component, setState } from "react";
import { View, StyleSheet } from "react-native";
import { isTablet } from "react-native-device-detection";
import { getRecipeCategories } from "../../api/recipeCategoriesApi";
import CategoryImageItem from "./CategoryImageItem";
import { getS3Image, deleteS3Image } from "../../utility/amplify";
class CategoryImagePicker extends Component {
state = {
categories: [],
};
async componentDidMount() {
console.log("loadCats");
let res = await fetch("http://192.168.1.4:4000/api/recipe_categories");
let categories = await res.json();
for (const category of categories) {
const imgUrl = await getS3Image(category.category_image);
category.imageUrl = imgUrl.split("?")[0];
}
console.log("returned");
this.setState({ categories });
console.log("set done...", categories);
}
loadViews() {
let i = 0;
let rowType = "odd";
let viewRows = [];
const { categories } = this.state;
console.log(categories.length);
while (i < categories.length) {
console.log("//", i);
if (isTablet) {
switch (rowType) {
case "odd":
if (i == categories.length - 1) {
viewRows.push(this.renderEvenRow(i, categories[i]));
} else {
viewRows.push(
this.renderOddRow(i, categories[i], categories[i + 1])
);
rowType = "even";
i += 2;
}
break;
case "even":
viewRows.push(this.renderEvenRow(i, categories[i]));
rowType = "odd";
i += 1;
break;
default:
break;
}
} else {
viewRows.push(this.renderEvenRow(i, categories[i]));
}
}
return viewRows;
}
handleShowRecipesForCategory = (category) => {
this.props.onShowRecipesForCategory(category);
};
renderOddRow(i, categoryLeft, categoryRight) {
return (
<View style={styles.oddRow} key={i}>
<CategoryImageItem
category={categoryLeft}
onShowRecipesForCategory={() =>
this.handleShowRecipesForCategory(categoryLeft)
}
/>
<CategoryImageItem
category={categoryRight}
onShowRecipesForCategory={() =>
this.handleShowRecipesForCategory(categoryRight)
}
/>
</View>
);
}
renderEvenRow(i, category) {
return (
<View style={styles.evenRow} key={i}>
<CategoryImageItem
category={category}
onShowRecipesForCategory={() =>
this.handleShowRecipesForCategory(category)
}
/>
</View>
);
}
render() {
return <View style={styles.container}>{this.loadViews()}</View>;
}
}
componentDidMount shouldn’t be async.
componentDidMount(){
this.getCategories()
}
getCategories = async () => {
console.log("loadCats");
let res = await fetch("http://192.168.1.4:4000/api/recipe_categories");
let categories = await res.json();
for (const category of categories) {
const imgUrl = await getS3Image(category.category_image);
category.imageUrl = imgUrl.split("?")[0];
}
console.log("returned");
this.setState({ categories });
console.log("set done...", categories);
}
If this doesn’t work can you add what you are getting from console.log("set done...", categories); and what you expected

React useState to update a specific item in its array

I have an array of images inside refData. I then map them into an array of img objects with RefItem. I then want thouse specifc images to change to the next one in a line - so img 0 becomes 1, and 1 becomes 2 - as written in refbox. I just cannot figure this one out.. I just want to update the individual numbers in each array item?
import React, {useState, useEffect} from 'react'
import refData from './refData'
import RefItem from './refItem'
function ReferencesPP(){
const refItems = refData.map(item => <RefItem key={item.id} pitem={item}/>)
const [refs, setRefs] = useState([0, 1, 2, 3])
useEffect(() => {
const intervalId = setTimeout(() => {
for(let i = 0; i < refs.length; i++){
if(refs[i] !== refData.length){
setRefs(...refs[i], refs[i] = refs[i] + 1)
} else {
setRefs(...refs[i], refs[i] = 0)
}
}
}, 2000);
return () => clearTimeout(intervalId);
}, [refs]);
return(
<div className="referencespp-container">
<div className="background-container" />
<div className="content-container">
<div id="refbox">
{refItems[refs[0]]}
{refItems[refs[1]]}
{refItems[refs[2]]}
{refItems[refs[3]]}
</div>
</div>
</div>
)
}
export default ReferencesPP
I thought it would be as simple, as writing
const refs = [0, 1, 2, 3]
useEffect(() => {
const intervalId = setInterval(() => {
for(let i = 0; i < refs.length; i++){
if(refs[i] !== refData.length){
refs[i] = refs[i] + 1;
} else {
refs[i] = 0;
}
}
}, 2000);
return () => clearInterval(intervalId);
}, []);
but doing that only updates the const and not the {refItems[refs[0]]} elements?
Have a look at this https://jsfiddle.net/gwbo4pdu/ I think it's what you want to get
React.useEffect(() => {
const intervalId = setTimeout(() => {
const newRefs = [...refs]
const i = newRefs.shift()
newRefs.push(i)
setRefs(newRefs);
}, 2000);
return () => clearTimeout(intervalId);
}, [refs]);
P.S. you can do just newRefs.push(newRefs.shift())

Partial and Dynamic data loading using pagination in React Bootstrap Table

I would like to update the data partially/dynamically using the pagination click.
codesandbox
On the initial load, I would like to render 100 records(DB has more data) and the pagination has to work normally without calling the API.
Once it user reaches the last page, at that time I would like to hit an API call to get another set of 100 data from the database to replace the existing.
From the above example, I can do either. Either by displaying the Static data with pagination on the initial loading of the application nor calling API on either of the pagination clicks.
Table.js
import React, { useState, useEffect } from "react";
import { BootstrapTable, TableHeaderColumn } from "react-bootstrap-table";
import Axios from "axios";
// import { products } from "./products";
import { PHOTOS } from "./constants";
import BSTable from "./BSTable";
function Table() {
/* Varibale Declaration */
const [userData, setUserData] = useState([]);
const [childData, setChildData] = useState();
const [currentPage, setCurrentPage] = useState();
const [sizePerPage, setSizePerPage] = useState();
/* Fetch User Data */
const fetchUserData = async (start = 0, limit = 50) => {
console.log("fetchUserData", start, limit);
var res = await Axios.get(
`https://jsonplaceholder.typicode.com/photos?_start=${start}&_limit=${limit}`
);
console.log("res", res);
setUserData(res.data);
};
/* React Hooks */
useEffect(() => {
fetchUserData();
}, []);
/* Functional Declaration */
let expandedRows = {};
const renderShowsTotal = (start, to, total) => {
return (
<p style={{ color: "blue" }}>
From {start} to {to}, totals is {total} (its a customize
text)
</p>
);
};
const handleExpand = async (rowKey, isExpand) => {
expandedRows = Object.assign(
expandedRows,
(expandedRows[rowKey] = isExpand)
);
var res = await Axios.get(
`https://jsonplaceholder.typicode.com/photos?_start=${0}&_limit=${1}`
);
console.log(res);
setChildData(res.data);
// expandedRows[rowKey] = isExpand;
// console.log(expandedRows[rowKey], Object.keys(expandedRows).length);
// if (!(Object.keys(expandedRows).length === 0)) {
// expandedRows = Object.assign({}, expandedRows[rowKey] = isExpand);
// } else {
// expandedRows[rowKey] = isExpand;
// }
console.log(rowKey, isExpand, expandedRows);
console.log("handleClick", isExpandableRow({ id: 4 }));
};
const onPageChange = (page, sizePerPage) => {
// setCurrentPage(page);
console.log("onPageChange", page, sizePerPage, userData);
// return false;
// fetchUserData((page - 1) * 10, sizePerPage);
// fetchUserData();
};
// const onSizePerPageList = sizePerPage => {
// console.log("onSizePerPageList", currentPage, sizePerPage);
// // const currentIndex = (currentPage - 1) * sizePerPage;
// // setUserData(userData.slice(currentIndex, currentIndex + sizePerPage));
// // setSizePerPage(sizePerPage);
// };
const onSortChange = (sortName, sortOrder) => {
console.log(sortName, sortOrder);
if (sortOrder === "asc") {
userData.sort((a, b) => {
if (parseInt(a[sortName], 10) > parseInt(b[sortName], 10)) {
return 1;
} else if (parseInt(b[sortName], 10) > parseInt(a[sortName], 10)) {
return -1;
}
return 0;
});
} else {
userData.sort((a, b) => {
if (parseInt(a[sortName], 10) > parseInt(b[sortName], 10)) {
return -1;
} else if (parseInt(b[sortName], 10) > parseInt(a[sortName], 10)) {
return 1;
}
return 0;
});
}
};
const options = {
onlyOneExpanding: true,
// onSizePerPageList: onSizePerPageList,
//hideSizePerPage: true,
paginationSize: 1,
onExpand: handleExpand,
sizePerPageList: [15, 10],
// sizePerPageList: [
// {
// text: "5",
// value: 5
// },
// {
// text: "10",
// value: 10
// },
// {
// text: "All",
// value: userData.length
// }
// ],
onPageChange: onPageChange,
sizePerPage: 15, // which size per page you want to locate as default
// pageStartIndex: 0, // where to start counting the pages
// paginationSize: 3, // the pagination bar size.
prePage: "Prev", // Previous page button text
nextPage: "Next", // Next page button text
firstPage: "First", // First page button text
lastPage: "Last", // Last page button text
totalSize: 100,
paginationShowsTotal: renderShowsTotal, // Accept bool or function
paginationPosition: "top", // default is bottom, top and both is all available
//hideSizePerPage: true, // You can hide the dropdown for sizePerPage
alwaysShowAllBtns: true, // Always show next and previous button
withFirstAndLast: currentPage === 4 ? true : false, // Hide the going to First and Last page button
expandRowBgColor: "rgb(242, 255, 163)",
expandBy: "column", // Currently, available value is row and column, default is row
expandBodyClass: function(row, rowIndex, isExpanding) {
if (!isExpanding) {
return "current-is-hidden";
} else {
if (rowIndex > 1) {
return "custom-expand-body-1";
} else {
return "custom-expand-body-0";
}
}
},
expandParentClass: function(row, rowIndex, isExpanding) {
return isExpandableRow(row);
},
onSortChange: onSortChange
};
const isExpandableRow = row => {
// console.log("isExpandableRow", row);
if (row.id < 3) return true;
else return false;
};
const expandComponent = (row, e) => {
console.log("expandComponent", row, e);
if (row.id === true) {
return <BSTable data={childData} />;
}
};
const expandColumnComponent = ({ isExpandableRow, isExpanded }) => {
let content = "";
console.log("expandColumnComponent", isExpandableRow, isExpanded);
if (isExpandableRow) {
content = isExpanded ? "(-)" : "(+)";
} else {
content = " ";
}
return <div> {content} </div>;
};
const expandedColumnHeaderComponent = ({ anyExpand }) => {
const content = anyExpand ? "(-)" : "(+)";
return <div>{content}</div>;
};
const trClassFormat = (rowData, rIndex) => {
return !isExpandableRow(rowData) ? "disable-row-cell" : "";
};
return (
<div className="react-bootstrap-table container mt-4">
<h2>React Bootstrap Table with pagination</h2>
{userData.length > 0 ? (
<BootstrapTable
dataSort={true}
hover
// remote={true}
fetchInfo={{ dataTotalSize: 1000 }}
trClassName={trClassFormat}
data={userData}
pagination
options={options}
expandableRow={isExpandableRow}
expandComponent={expandComponent}
expandColumnOptions={{
expandColumnVisible: true,
expandColumnComponent: expandColumnComponent,
columnWidth: 50,
expandedColumnHeaderComponent: expandedColumnHeaderComponent
}}
autoCollapse={{ sort: true, search: true, filter: true }}
search
>
<TableHeaderColumn expandable={false} dataField="id" isKey={true}>
User ID
</TableHeaderColumn>
<TableHeaderColumn expandable={false} dataField="title">
User Title
</TableHeaderColumn>
<TableHeaderColumn expandable={false} dataField="url">
User URL
</TableHeaderColumn>
</BootstrapTable>
) : (
<i className="fa fa-spinner fa-3x fa-spin" />
)}
</div>
);
}
export default Table;
Thanks in advance.
You can use react-bootstrap-table2:
learn about react-bootstrap-table2 Pagination from this Documentation

How can the render be synced with the state on pagination click with react?

I created a suggestions search and its built to break up the fetch based on the current page. The state is console.loged correctly, but the render is one page click event behind. This is obviously not the behavior we want. It seems like the state is being updated fine. I have tried to refactor the code difference ways, and even tried this.forceUpdate()
Here is the code
SearchOrderBar.js
import React, { Component } from "react";
import {Input, Label, Table, Icon, Header, Menu} from 'semantic-ui-react';
import "./SearchOrderBar.css";
// import { resolve } from "dns";
// import PropTypes from 'prop-types';
import Pagination from '../Search/Pagination';
class SearchOrderBar extends Component {
constructor(props) {
super(props);
this.text = "";
this.state = {
suggestions: [],
addToQuery: false,
Query: [],
pagesNeeded: 0,
page: 1
};
let searchTerm = null;
const {pageLimit = null, keyTimer = null, } = props;
this.pageLimit = typeof pageLimit === 'number' ? pageLimit : 10;
this.handlePageClick = this.handlePageClick.bind(this);
this.fetchCallBack = this.fetchCallBack.bind(this);
// this.addToQuery = this.addToQuery.bind(this);
this.keyUpHandler = this.keyUpHandler.bind(this);
this.keyDownHandler = this.keyDownHandler.bind(this);
}
handlePageClick(page){
this.forceUpdate();
this.setState({
page: page
})
this.fetchCallBack();
}
//This fetch should be called in a dynamic switch case
fetchCallBack() {
let y = this.pageLimit;
let x = this.state.page > 1 ? (this.pageLimit*this.state.page) - this.pageLimit : 0;
// Return a promise
return new Promise((resolve, reject) => {
let searchTerm = this.searchTerm;
return fetch(`http://localhost:5000/api/searchorders/${searchTerm}/${x}/${y}`)
.then(res => {
if (!res.ok) {
throw res;
}
// Convert serialized response into json
return res.json()
}).then(data => {
//Use data
let searchTerm = data.map(data => {
let rData = {};
rData = data;
return rData;
})
this.item = searchTerm;
//console.log('here from callback')
this.setState({
suggestions: []
})
return searchTerm;
}).then( data => {
// console.log(this.totalRecords)sd
//console.log(data)
if (searchTerm.length === 0) {
this.setState({
suggestions: [],
rangeCount_URL: `http://localhost:5000/api/searchorderscount/${searchTerm}`
});
} else {
const suggestions = data.filter(function(v){
if(Object.values(v).includes(searchTerm.toLowerCase()) !== -1 || Object.values(v).includes(searchTerm.toUpperCase()) !== -1){
return v
}
})
console.log(suggestions)
this.text = searchTerm;
this.setState({ suggestions: suggestions.sort()});
}
})
})
}
pageCountCallBack(){
return new Promise((resolve, reject) => {
let searchTerm = this.searchTerm;
return fetch(`http://localhost:5000/api/searchorderscount/${searchTerm}/`)
.then(res => {
if (!res.ok) {
throw res;
}
// Convert serialized response into json
return res.json()
}).then(data => {
//Use data
let searchTerm = data.map(data => {
let rData = {};
rData = data;
return rData;
})
this.item = searchTerm;
// console.log('here from Page Count callback')
this.renderSuggestions();
resolve(searchTerm)
})
})
}
keyUpHandler = (e) => {
if(e.target.value.length >= 3){
this.keyTimer = setTimeout(this.countFetch(e), 1500);
} else {
this.setState(() => {
return {
suggestions : [],
pagesNeeded : 0
}
})
clearTimeout(this.keyTimer);
}
}
keyDownHandler = (e) => {
clearTimeout(this.keyTimer);
}
//Any time text is changed in the text field
countFetch = (e) => {
const value = e.target.value;
this.searchTerm = value;
this.pageCountCallBack().then(data => {
const totalRecords = data[0].rows;
this.setState(() => {
return {pagesNeeded : Math.ceil(totalRecords / this.pageLimit)}
})
//console.log("total" + totalRecords);
//console.log("page limit"+this.pageLimit);
//console.log("Needed" + this.state.pagesNeeded );
})
this.fetchCallBack();
}
renderSuggestions() {
//const { suggestions } = this.state;
const tableStyle = {
'tableLayout': 'fixed',
'overflowWrap': 'break-word'
}
return (
<Table style={tableStyle} celled>
{this.state.suggestions.length === 0 ?
(<Table.Body>
<Table.Cell colSpan="7">
<div className="ui fluid warning icon message">
<Icon name="exclamation triangle" size="huge" color="orange"/>
<div className="content">
<Header>No Records Found</Header>
<p>Try Seaching by one of the following:</p>
<ul>
<dt>Name</dt>
<dt>Order Number</dt>
<dt>Address (Shipping or Billing )</dt>
<dt>Phone Number</dt>
<dt>Email</dt>
</ul>
</div>
</div>
</Table.Cell>
</Table.Body>)
: (
<>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Order#</Table.HeaderCell>
<Table.HeaderCell>Billing Address</Table.HeaderCell>
<Table.HeaderCell>Shipping Address</Table.HeaderCell>
<Table.HeaderCell>Email</Table.HeaderCell>
<Table.HeaderCell>Phone Number</Table.HeaderCell>
<Table.HeaderCell>Sales Channel</Table.HeaderCell>
<Table.HeaderCell>Order Date</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{this.state.suggestions.map((item, index) => (
<Table.Row className="hoverRow">
<Table.Cell key={index} onClick={() => this.addToQuery(item)}>
{item.customerPO}
</Table.Cell>
<Table.Cell>
{item.billToAddress}
</Table.Cell>
<Table.Cell>{item.shipToAddress}</Table.Cell>
<Table.Cell>{item.email}</Table.Cell>
<Table.Cell>{item.phone}</Table.Cell>
<Table.Cell>{item.customerContact}</Table.Cell>
<Table.Cell>{item.dateCreated}</Table.Cell>
</Table.Row>
))}
</Table.Body>
</>
)
}
<Pagination key={this.state.pagesNeeded} tableCols="7" pagesNeeded={this.state.pagesNeeded} btnLimit={5} pageClick={this.handlePageClick} currPage={this.state.page} pageLimit={this.pageLimit}/>
</Table>
);
}
handleIconClick(){
console.log('icon clicked ' + this.state.Query )
}
render() {
const {text} = this.state
//console.log(this.state)
return (
<>
<div className="App-Component">
<div className="App-Search">
<Input icon={{ name: 'search', circular: true, link: true, onClick: () => this.handleIconClick() }} placeholder="Search" value={text} type="text" onKeyUp={this.keyUpHandler} onKeyDown={this.keyDownHandler} className="App-Search"/>
{this.renderSuggestions()}
</div>
</div>
</>
);
}
}
export default SearchOrderBar;
Here is the pagination but I don't think this matters as much for the solution. It is relevant for the page button click.
import React, {Component} from 'react';
import {Input, Label, Table, Icon, Header, Menu} from 'semantic-ui-react'
/**
* Helper Method for creating a range of Numbers
* Range )( )
*/
const range = (from, to, step = 1) => {
let i = from;
const range = [];
while (i<=to) {
range.push(i);
i+=step;
}
}
export default class Pagination extends Component {
constructor(props){
super(props)
const { totalRecords = null, pageNeighbours = 0, rangeCount_URL = this.props.rangeCount_URL, pageArray = [] } = props;
this.pageArray = typeof pageArray === 'array' ? pageArray : [];
}
renderPagination = () => {
//console.log("hello from pagination");
let n = this.props.pagesNeeded;
let pArray = [];
let page = this.props.currPage;
//console.log(n)
if (page > 1){
pArray.push(<Menu.Item as='a' icon onClick={() => this.props.pageClick(page-1)}>
<Icon name='chevron left' />
</Menu.Item>)
}
for(let i = (page >1 ? page-1: page); pArray.length < (page > this.props.btnLimit ? this.props.btnLimit+1 : this.props.btnLimit); i++){
//console.log(i);
pArray.push(<Menu.Item index={i} className={i == page ? 'active' : ''} onClick={() => this.props.pageClick(i)} as='a'>{i}</Menu.Item>)
}
if (page < n){
pArray.push(<Menu.Item as='a' icon onClick={() => this.props.pageClick(page+1)}>
<Icon name='chevron right' />
</Menu.Item>)
}
this.pageArray = pArray;
return pArray;
}
render(){
const pageCount = (() => {
const totalRecords = this.totalRecords;
if(totalRecords > 0){
return (this.totalPages = Math.ceil(this.totalRecords / this.props.pageLimit))
}
})();
//console.log(this.pageArray);
return(
<Table.Footer>
{ this.props.pagesNeeded > 1 &&
<Table.Row>
<Table.HeaderCell colSpan={this.props.tableCols}>
<Menu floated='right' pagination>
{this.renderPagination()}
</Menu>
</Table.HeaderCell>
</Table.Row>
}
</Table.Footer>
)
}
}
setState is batched and invoked asynchronously, meaning when you call to this.setState({page}) then read this.state.page in fetchCallBack you probably get the "old" page and not the new page.
Either pass the page directly to fetchCallBack
this.fetchCallBack(page)
And read the page from it and not directly from the state
Or call it as the second argument of setState which is a callback that react will invoke right after the state has been updated.
this.setState({ page }, this.fetchCallBack);
At the point fetchCallBack is called, this.state.page is not updated yet because setState is called asynchronously, that's why it's using the old value. Try this:
handlePageClick(page) {
this.setState({ page }, this.fetchCallBack);
}
The callback syntax allows you to run the function in the next iteration.

React persistent state with React Router 4

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

Categories

Resources