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
Related
wassup Folks,
I got the following error from my component called "SearchReactTableContainer". I looked inside but everything looks well to many. Any Ideas how to fix this? I looked into the network conditions and the needed data from my resulting query is provided without any issues to my frontend.
SearchReactTableContainer.js
import React, { Component, Fragment } from "react";
import { has, isNil } from "ramda";
import CockpitSearchInput from "#components/SearchInput";
import SearchReactTable from "#cockpitComponents/SearchReactTable";
import {
MY_CASES_FILTER_KEY,
UNASSIGNED_FILTER_KEY
} from "#modules/CaseManager/CaseManagerDashboard/constants";
import { Col, Row } from "antd";
// TODO: TO BE REMOVED
const pagesCount = 3855;
export default class SearchReactTableContainer extends Component {
constructor(props) {
super(props);
const { limit, searchValue } = this.props;
const orMyCases = localStorage.getItem(MY_CASES_FILTER_KEY);
const orUnassignedCases = localStorage.getItem(UNASSIGNED_FILTER_KEY);
this.state = {
loading: false,
searchValue: searchValue || "",
data: [],
pageSize: limit,
pagesCount,
orMyCases: this.parseNonNullValue(orMyCases),
orUnassignedCases: this.parseNonNullValue(orUnassignedCases)
};
this.triggerFetchMore = this.triggerFetchMore.bind(this);
}
parseNonNullValue(value) {
try {
return !isNil(value) ? JSON.parse(value) : true;
} catch (err) {
console.error(err);
return false;
}
}
async fetchMoreFromGraphql(fetchMore, variables, tableKey) {
return await fetchMore({
variables,
updateQuery: (previousResult, { fetchMoreResult }) => {
if (!fetchMoreResult || !previousResult) return previousResult;
if (!(tableKey in previousResult)) {
return [];
}
return {
...previousResult,
...fetchMoreResult[tableKey]
};
}
});
}
async onFetchDataHandler(state) {
const { page, pageSize, sorted, filtered } = state;
const { tableKey, fetchMore } = this.props;
const { searchValue, orMyCases, orUnassignedCases } = this.state;
this.changeTableLoadingState();
const response = await this.fetchMoreFromGraphql(
fetchMore,
{
searchValue,
offset: page * pageSize,
limit: pageSize,
page,
pageSize,
sorted,
filtered,
filters: {
orMyCases,
orUnassignedCases
}
},
tableKey
);
let { data } = response;
const pagesCount =
searchValue.length === 0
? has("totalCount", data[tableKey])
? Math.ceil(data[tableKey].totalCount / pageSize)
: has("items", data[tableKey])
? Math.ceil(data[tableKey].items.length / pageSize)
: Math.ceil(data[tableKey].length / pageSize)
: this.state.pagesCount;
this.setState({
...this.state,
loading: false,
data: data[tableKey].items ? data[tableKey].items : data[tableKey],
pageIndex: page,
pageSize,
pagesCount
});
}
async triggerFetchMore(searchValue = "") {
const { tableKey, fetchMore, tableName } = this.props;
const { orMyCases, orUnassignedCases } = this.state;
this.changeTableLoadingState();
const variables = {
searchValue,
offset: 0,
limit: this.state.pageSize,
filters: {
orMyCases,
orUnassignedCases
}
};
const response = await this.fetchMoreFromGraphql(fetchMore, variables, tableKey);
const { data } = response;
try {
await this.setState(prevState => {
const { pageSize } = prevState;
const pagesCount =
searchValue.length === 0
? has("totalCount", data[tableKey])
? Math.ceil(data[tableKey].totalCount / pageSize)
: has("items", data[tableKey])
? Math.ceil(data[tableKey].items.length / pageSize)
: Math.ceil(data[tableKey].length / pageSize)
: this.state.pagesCount;
prevState = {
...prevState,
loading: false,
data: data[tableKey].items ? data[tableKey].items : data[tableKey],
searchValue,
pagesCount
};
return prevState;
});
localStorage.setItem(`${tableName}.searchValue`, searchValue);
} catch (err) {
console.log(err);
}
}
changeTableLoadingState() {
this.setState({ loading: !this.state.loading });
}
render() {
const {
columns,
dataFormatter,
search = true,
defaultSorted,
filterBlock
} = this.props;
const { data, pagesCount, pagesSize, loading, searchValue } = this.state;
let formattedData = data;
if (typeof dataFormatter === "function") {
formattedData = dataFormatter(data);
}
return (
<Fragment>
{search && (
<Row>
<Col span={6}>
<CockpitSearchInput
onChange={this.triggerFetchMore}
initialValue={searchValue}
/>
</Col>
</Row>
)}
{!isNil(filterBlock) && filterBlock(this)}
<SearchReactTable
{...this.props}
manual
data={formattedData}
columns={columns}
loading={loading}
// Request new data when things change
onFetchData={(state, instance) => this.onFetchDataHandler(state, instance)}
pages={pagesCount} // Display the total number of pages
pageSizeOptions={[5, 10, 20, 25, 50, 100]}
defaultPageSize={pagesSize}
className="table table-striped table-bordered table-hover"
defaultSorted={defaultSorted}
/>
</Fragment>
);
}
}
I'll add a screenshot showing me the actual issue in the developer tab.
Component that uses SearchReactTable
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.
Front End - Front End
Upon clicking the star, I want to update the state of nested object, with the new rating value of star.
I tried many things but it didnt work as states are immutable.
Nested State
Can some upon please suggest how can I update the value in nested object
onStarClicked = (kTypName, subItemId1, newRating) => {
//console.log(subItemId.split("_"));
let evaluation = subItemId1.split("_")[0];
let subItemId = subItemId1.split("_")[1];
console.log(subItemId);
const r = { ...this.state.ratings };
let kT = r.knowledgeTypes;
let sub = '', kTN = '', kIN = '';
kT.map(knowledgeType => {
//console.log(knowledgeType.knowledgeTypeId);
knowledgeType.knowledgeItems.map(knowledgeItem => {
//console.log(knowledgeItem.knowledgeItemId);
knowledgeItem.subItems.map(knowledgeSubItem => {
//console.log(knowledgeSubItem.subItemId);
if (subItemId === knowledgeSubItem.subItemId) {
kTN = knowledgeType.knowledgeTypeName;
kIN = knowledgeItem.knowledgeItemName;
sub = knowledgeSubItem;
if (evaluation === "self") {
sub.evaluation.self.rating = newRating;
}
else if (evaluation === "evaluator") {
sub.evaluation.evaluator.rating = newRating;
}
//alert(evaluation + subItemId + ' ' + newRating);
//return;
}
})
})
});
this.setState({
...this.state,
ratings: {
...this.state.ratings,
knowledgeTypes: [
...this.state.ratings.knowledgeTypes,
this.state.ratings.knowledgeTypes.filter(kt => kt.knowledgeTypeName !== kTN),
{
...this.state.ratings.knowledgeTypes.knowledgeItems.
filter(ki => ki.knowledgeItemName !== kIN),
knowledgeItems: {
...this.state.ratings.knowledgeTypes.knowledgeItems.subItems.
filter(si => si.subItemId !== subItemId),
sub
}
}]
}
});
}
You basically have to create a new empty array of knowledgeTypes and use the current state to find which item of the state you need to change using Object.keys/map/filter functions.
You'd use the current state in a variable and modify that variable only. You'd likely not mess with the actual state object in any way.
After you have done that, simply append it to the empty array. Then you can setState() the new array to the actual state property.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
financialYear: "2019-20",
quarter: "Q1",
isCurrentQuarter: true,
knowledgeTypes: [
{
knowledgeTypeName: "Technology",
knowledgeItems: [
{
knowledgeItemName: "Java",
subItems: [
{
subItemId: "2",
subItemName: "Collections",
evaluation: {
self: {
ntnet: "Joe",
rating: 1,
isEditable: true
}
}
}
]
}
]
}
]
};
}
handleClick = e => {
const { knowledgeTypes } = this.state;
// transformation
const itemToChange = knowledgeTypes.map(item => {
if (item.knowledgeTypeName === "Technology") {
return item;
}
});
const currItems = itemToChange[0].knowledgeItems[0].subItems;
const subItem = currItems.map(item => {
if (item.subItemId === "2") {
return item;
}
});
const person = subItem[0].evaluation;
person.self.rating = 55; //change
const newKnowledgeTypes = [];
knowledgeTypes.map(item => {
if (item.knowledgeTypeName === "Technology") {
newKnowledgeTypes.push(itemToChange);
}
newKnowledgeTypes.push(item);
});
this.setState({
knowledgeTypes: newKnowledgeTypes
});
console.log(this.state);
};
render() {
return (
<div>
MyComponent
<button onClick={this.handleClick}>Hello</button>
</div>
);
}
}
The sandbox can be found on https://codesandbox.io/s/musing-dew-8r2vk.
Note: It is advisable you do not use nested state objects because state objects are something more lightweight so that they do not have performance considerations.
import React, { Component } from 'react';
import Auxilary from '../../../hoc/Auxilary/auxilary';
import KnowledgeItems from '../KnowledgeItems/KnowledgeItems';
import Tabs from 'react-bootstrap/Tabs';
import Tab from 'react-bootstrap/Tab';
import knowledge from '../../../assests/staticdata.json';
import './QuarterLog.css';
class QuarterLog extends Component {
constructor() {
super();
this.state = {
"financialYear": "",
"quarter": "",
"isCurrentQuarter": "",
"knowledgeTypes": []
}
}
onStarClicked = (kTypName, kItemName, subItemIdName, newRating) => {
let evaluation = subItemIdName.split("_")[0];
let subItemId = subItemIdName.split("_")[1];
const { knowledgeTypes } = this.state;
// transformation
let knowledgeTypeToChange = knowledgeTypes.map(kType => {
if (kType.knowledgeTypeName === kTypName) {
return kType;
}
});
knowledgeTypeToChange = knowledgeTypeToChange.filter(function (element) {
return element !== undefined;
});
console.log(knowledgeTypeToChange[0]);
let knowledgeItemToChange = knowledgeTypeToChange[0].knowledgeItems.map(item => {
if (item.knowledgeItemName === kItemName) {
return item;
}
});
knowledgeItemToChange = knowledgeItemToChange.filter(function (element) {
return element !== undefined;
});
let knowledgeSubItem = knowledgeItemToChange[0].subItems.map(subItem => {
if (subItem.subItemId === subItemId) {
return subItem;
}
});
knowledgeSubItem = knowledgeSubItem.filter(function (element) {
return element !== undefined;
});
console.log(knowledgeSubItem);
let personEvaluations = knowledgeSubItem[0].evaluation;
if (evaluation === "self") {
personEvaluations.self.rating = newRating.toString(); //change
}
else if (evaluation === "evaluator") {
personEvaluations.evaluator.rating = newRating.toString(); //change
}
const newKnowledgeTypes = [];
knowledgeTypes.map(item => {
if (item.knowledgeTypeName === kTypName) {
newKnowledgeTypes.push(knowledgeTypeToChange[0]);
}
else
newKnowledgeTypes.push(item);
});
this.setState({
knowledgeTypes: newKnowledgeTypes
});
console.log(this.state);
}
componentDidMount() {
// TODO: remove staticdata.js and call REST API and set the response in state
this.setState({
...this.state,
"financialYear": knowledge.financialYear,
"quarter": knowledge.quarter,
"isCurrentQuarter": knowledge.isCurrentQuarter,
"knowledgeTypes": knowledge.knowledgeTypes
})
}
onSubmitRatings = () => {
console.log(this.state);
}
render() {
let data = knowledge; //remove this code, once REST API is implemented
const posts = this.state.knowledgeTypes.map(knowledgeType => {
return (
<Tab key={knowledgeType.knowledgeTypeName} eventKey={knowledgeType.knowledgeTypeName}
title={knowledgeType.knowledgeTypeName}>
<KnowledgeItems
kTypeName={knowledgeType.knowledgeTypeName}
kItems={knowledgeType.knowledgeItems}
ratings={this.state.ratings}
onstarclicked={this.onStarClicked}
/>
</Tab>)
});
return (
<Auxilary>
<div className="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<div><h1>Financial Year : {data.financialYear}</h1></div>
<div><h2>Quarter : {data.quarter}</h2></div>
</div>
<div>
<Tabs defaultActiveKey="Domain" id="uncontrolled-tab-example">
{posts}
</Tabs>
</div>
<button onClick={this.onSubmitRatings}> Submit </button>
</Auxilary>
);
}
}
export default QuarterLog;
Disclaimer: I've seen Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null but no solution from this question fixes my problem
I am trying to render a component inside a React route like this in App.js
<main style={{marginTop: '1px'}}>
<div className="App">
<Switch>
<Route exact path ='/' render = {() => <Home toggleFilter={this.state.filterDrawerOpen}/>}/>
<Route path='/profile' component ={Profile}/>
</Switch>
</div>
</main>
But the Home component never get's rendered even tho I can see console logs from the Home component render in the console like in this picture console logs
The home component is 400+ lines long so I'll include just the relevant code
import React, { Component } from 'react';
import Auth from '#aws-amplify/auth';
import { API } from 'aws-amplify';
import ProfileRedirect from "./components/ProfileRedirect";
import LoadingAnimation from './components/LoadingAnimation';
import ReadingSpeed from "./components/ReadingSpeed";
import './Tags.css';
import Articles from "./components/Articles";
import { CSSTransitionGroup } from 'react-transition-group'
import FilterArea from './components/FilterArea';
import "react-datepicker/dist/react-datepicker.css";
import FilterDrop from './components/FilterDrop';
import FilterDrawer from './components/FilterDrawer';
import { withRouter } from "react-router";
let apiName = 'userApi';
let path = '/users/';
class Home extends Component {
constructor(props){
super(props);
this.state = {
isLoading: true,
firstLogIn: true,
filterDrawerOpen:false,
user :{
phone:"",
readingSpeed:0,
email:"",
username:"",
articles: [],
interests: [],
saved:[],
filters:{
minutes:null,
authors:[],
sources:[],
minDate:{},
selectedInterests:[],
selectedDate:{}
}
}
}
this.dateFilter = this.dateFilter.bind(this)
}
async componentDidMount(){
let userEntry;
let date = new Date();
date.setMonth(date.getMonth()-1)
// const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const loggedUser = await Auth.currentAuthenticatedUser();
userEntry = await API.get(apiName,path + loggedUser.username);
if(userEntry.hasOwnProperty("userName")){
let uniqueResults;
let results = await this.callDatabase(userEntry)
uniqueResults = results.reduce(function (p, c) {
if (!p.some(function (el) {return (el.title === c.title && el.author === c.author);}))
p.push(c);
return p;
}, []);
this.setState({
isLoading:false,
firstLogIn:false,
filterDrawerOpen:false,
user : {
phone:userEntry.userName,
readingSpeed:userEntry.readingSpeed,
email:userEntry.userEmail,
username:userEntry.userName,
articles: uniqueResults,
interests:userEntry.userInterests,
saved: userEntry.savedArticles,
filters:{
minutes:null,
authors:[],
sources:[],
minDate:date,
selectedDate:{},
selectedInterests:[]
}
}
})
}else {
this.setState({
isLoading:false
})
}
}
async callDatabase (userEntry,sources,freeMode){...}
authorFilter = selected => {...}
sourceFilter = selected => {...}
interestFilter = selected => {...}
minutesFilter(value) {...}
componentWillReceiveProps(newProps) {
if(newProps.toggleFilter !== this.props.toggleFilter){
this.filterToggleClickHandler();
}
}
filterToggleClickHandler = () => {...}
filterDropClickHandler = () => {...}
dateFilter(selected) {...}
generateOptions = filter => {
let data;
if (filter === "author"){
data = this.state.user.articles.reduce(function (p, c) {
if (!p.some(function (el) { return (el.author === c.author); }))
p.push(c);
return p;
}, [])
}else if (filter==="source"){
let tempData;
tempData = this.state.user.articles.reduce(function (p, c) {
if (!p.some(function (el) { return (el.source.name === c.source.name); }))
p.push(c);
return p;
}, [])
data = tempData.map(element => element.source)
}else if (filter==="interest"){
data = this.state.user.articles.reduce(function (p, c) {
if (!p.some(function (el) { return (el.interest === c.interest); }))
p.push(c);
return p;
}, [])
}
return data
}
async updateDataBase(readingSpeed){
let updates = {
body:{
userName:this.state.user.username,
userEmail:this.state.user.email,
userPhone:this.state.user.phone,
userInterests:this.state.user.interests,
savedArticles:this.state.user.saved,
readingSpeed:readingSpeed,
}
}
return await API.put(apiName,path,updates);
}
filtersArea() {
let check
let newDate = this.state.user.filters.selectedDate;
if(newDate === null){
check = true
}else {
check= Object.entries(newDate).length === 0 && newDate.constructor === Object
}
return (
<div className="container-fluid">
<div className="col" style={{margin:"0",padding:"6"}}>
<FilterArea
sourceOptions = {this.generateOptions("source")}
interestOptions = {this.generateOptions("interest")}
authorOptions = {this.generateOptions("author")}
sourceFilter = {this.sourceFilter.bind(this)}
interestFilter = {this.interestFilter.bind(this)}
authorFilter = {this.authorFilter.bind(this)}
selected={!check ? this.state.user.filters.selectedDate:undefined}
minDate = {this.state.user.filters.minDate}
dateFilter = {this.dateFilter.bind(this)}
minutesFilter = {this.minutesFilter.bind(this)}
/>
<FilterDrawer
show = {this.state.filterDrawerOpen}
sourceOptions = {this.generateOptions("source")}
interestOptions = {this.generateOptions("interest")}
authorOptions = {this.generateOptions("author")}
sourceFilter = {this.sourceFilter.bind(this)}
interestFilter = {this.interestFilter.bind(this)}
authorFilter = {this.authorFilter.bind(this)}
selected={!check ? this.state.user.filters.selectedDate:undefined}
minDate = {this.state.user.filters.minDate}
dateFilter = {this.dateFilter.bind(this)}
minutesFilter = {this.minutesFilter.bind(this)}
/>
</div>
</div>
);
}
checkAuthors(filter,data){
let result = [];
let articles = data.map(function(article){
if(filter.includes(article.author))result.push(article);
})
return result
}
checkSource(filter,data){
let result = [];
let articles = data.map(function(article) {
if(filter.includes(article.source.name)) result.push(article)
})
return result
}
checkInterest(filter,data){
let result = [];
let articles = data.map(function(article){
if(filter.includes(article.interest))result.push(article);
})
return result
}
checkMinutes(filter,filter1,data){
let result = [];
let articles = data.map(function (article) {
if(article.hasOwnProperty("charNumber")){
if((article.charNumber/filter1)<=filter)result.push(article)
}
})
return result
}
checkDate(filter,data){
let result = [];
let dA;
let dB = filter;
let articles = data.map(function(article){
dA = new Date(article.publishedAt.substring(0,10))
if(dB<=dA) result.push(article)
})
return result
}
render() {
let filterdrop;
if(this.state.filterDrawerOpen) {
filterdrop = <FilterDrop click = {this.filterDropClickHandler}/>
}
console.log(this.state)
const stillLoading = () => {
return (
<div className="loading">
<LoadingAnimation
type = {"spinningBubbles"}
color = {"aqua"}
/>
</div>);
}
const articles = (filterA, filterS, filterI, filterM) => {
let articles = this.state.user.articles;
let newDate = this.state.user.filters.selectedDate;
let readingTime = this.state.user.readingSpeed;
let check;
if(newDate === null){
check = true
}else {
check= Object.entries(newDate).length === 0 && newDate.constructor === Object
}
if(!check){
articles = this.checkDate(newDate,articles)
}
if(filterA.length){
articles = this.checkAuthors(filterA,articles)
}
if(filterS.length){
articles = this.checkSource(filterS,articles)
}
if(filterI.length){
articles = this.checkInterest(filterI,articles)
}
if(!(filterM === null) && filterM!==0){
articles = this.checkMinutes(filterM,readingTime,articles)
}
return(
<div className="wrapper">
{
articles.map(function(article) {
return (
<Articles
article = {article}
key = {article.id}
/>
)
})}
</div> );
}
if(this.state.isLoading){
return (
stillLoading()
);
}else if(!this.state.firstLogIn && this.state.user.articles.length>0 && this.state.user.readingSpeed >0){
return (
<CSSTransitionGroup
transitionName="example"
transitionAppear={true}
transitionAppearTimeout={1000}
transitionEnter={false}
transitionLeave={false}>
{this.filtersArea()}
{filterdrop}
{articles(this.state.user.filters.authors,this.state.user.filters.sources,
this.state.user.filters.selectedInterests,this.state.user.filters.minutes)}
</CSSTransitionGroup>
);
}else if(this.state.firstLogIn || this.state.user.readingSpeed === 0){
return(
<ReadingSpeed updateDb = {this.updateDataBase.bind(this)}/>
);
}
else if(this.state.user.interests.length === 0){
return(
<div>
<ProfileRedirect/>
</div>
);
}
}
}
export default Home;
I've tried things like
const MyHome =(props) => {
return (
<Home {...props} toggleFilter = {this.state.filterDrawerOpen}/>
)
}
Instead of rendering directly in the Route but nothing seems to work.
Here is the code for index.js as well
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import 'bootstrap/dist/css/bootstrap.css';
import {BrowserRouter as Router, Route} from 'react-router-dom';
ReactDOM.render(
<Router>
<Route path='/' render={(props )=> <App {...props}/>}/>
</Router>,
document.getElementById('root'));
serviceWorker.unregister();
What am I missing?
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