ComponentDidUpdate causing infinite render even when wrapped in condition - reactjs

I'm trying to pull some data from Google Analytics reporting API and display the data inside the apex charts library. I managed to successfully do this. However, I now want filtering options to where if the user selects a certain date within the date range picker react wrapper, the apex charts data gets updated from the API.
I'm struggling figuring out on how when my data gets updated, to update the state with the new state within my life cycle method? I think I'm doing something minor I just don't know what it is. I looked up the documentation on the life cycle method and it says to make sure to wrap it inside a condition which I did. However, when the else condition is met, it causes an infinite render.
Here is my code: (the bug i'm stuck on is the componentWillUpdate lifecycle method) everything else works fine.
import React from "react";
import Header from "../common/Header";
import Footer from "../common/Footer";
import moment from "moment";
import $ from "jquery";
import ApexCharts from "apexcharts";
import Chart from "react-apexcharts";
import DateRangePicker from "react-bootstrap-daterangepicker";
const VIEW_ID = "";
class Charts extends React.Component {
constructor(props) {
super(props);
this.printResults = this.printResults.bind(this);
this.pageViews = this.pageViews.bind(this);
this.handleError = this.handleError.bind(this);
this.state = {
loading: true,
filterstartDate: "",
filterendDate: "",
// Start Series Bar State
ChartOne: {
chart: {
id: "ChartOne"
},
colors: ["#e31d1a"],
xaxis: {
categories: [],
labels: {
style: {
colors: []
}
},
title: {
text: "Locations"
}
},
yaxis: {
labels: {
style: {
colors: []
}
},
title: {
text: "Count"
}
}
},
ChartOneSeries: [],
}
pageViews = async () => {
window.gapi.client
.request({
path: "/v4/reports:batchGet",
root: "https://analyticsreporting.googleapis.com",
method: "POST",
body: {
reportRequests: [
{
viewId: VIEW_ID,
dateRanges: [
{
startDate: "7daysAgo",
endDate: "today"
}
],
metrics: [
{
expression: "ga:pageviews"
}
],
dimensions: [
{
name: "ga:country"
}
],
orderBys: [{ fieldName: "ga:pageviews", sortOrder: "DESCENDING" }]
}
]
}
})
.then(this.printResults, this.handleError);
};
componentDidMount() {
$.getScript("https://apis.google.com/js/client:platform.js").done(() => {
window.gapi.signin2.render("my-signin2", {
scope: "profile email",
width: 240,
height: 50,
longtitle: true,
theme: "dark",
onsuccess: this.pageViews,
onfailure: this.handleError
});
});
}
//log the data
printResults(response) {
let pageviewLocation = [];
let pageviewCount = [];
let pageviewTotal = response.result.reports[0].data.totals[0].values[0];
let totalComma = pageviewTotal
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
response.result.reports[0].data.rows.map(value => {
//console.log(value.dimensions);
pageviewLocation.push(value.dimensions[0]);
pageviewCount.push(parseInt(value.metrics[0].values[0]));
});
//console.log(total);
this.setState({
loading: false,
ChartOne: {
title: {
text: totalComma,
align: "center",
style: {
fontSize: "20px"
}
},
subtitle: {
text: "Total Page Views",
align: "center",
style: {
fontSize: "14px",
cssClass: "apexcharts-yaxis-title"
}
},
plotOptions: {},
...this.state.ChartOne,
xaxis: {
width: 1,
...this.state.ChartOne.xaxis,
labels: {
show: false,
...this.state.ChartOne.xaxis.labels,
style: {
...this.state.ChartOne.xaxis.labels.style
}
},
categories: pageviewLocation
},
yaxis: {
min: 0,
...this.state.ChartOne.yaxis,
labels: {
//show: false,
...this.state.ChartOne.yaxis.labels,
style: {
...this.state.ChartOne.yaxis.labels.style
}
}
}
},
ChartOneSeries: [
{
name: "Total Page Views",
data: pageviewCount
}
]
});
}
componentDidUpdate(prevProps, prevState) {
if (this.state.filterstartDate === "" && this.state.filterendDate === "") {
console.log("they are empty");
} else {
this.setState({
// this fails immediately once the condition is met
test: "success!"
});
}
}
Datepicker = async (event, picker) => {
this.setState({
filterstartDate: moment(picker.startDate._d).format("YYYY-MM-DD"),
filterendDate: moment(picker.endDate._d).format("YYYY-MM-DD")
});
//console.log(this.state);
};
//or the error if there is one
handleError(reason) {
console.error(reason);
console.error(reason.result.error.message);
}
render() {
//console.log();
return (
<div className="containerfluid" id="fullWidth">
<Header />
<div className="container" id="chartContainer">
<h1>Site Analytics</h1>
<div className="row">
<div className="col-md-12">
<DateRangePicker
startDate={moment().format("MM-DD-YYYY")}
endDate={moment().format("MM-DD-YYYY")}
onApply={this.Datepicker}
>
<button className="btn btn-info">
<i className="fas fa-filter">
<span
style={{
fontFamily: "Roboto, san-serif",
fontWeight: "normal",
padding: "5px"
}}
>
Filter Date
</span>
</i>
</button>
</DateRangePicker>
</div>
</div>
<div className="row">
<div className="col-md-4">
{/* Chart One Line */}
{this.state.loading ? (
<React.Fragment>
<i className="fas fa-spinner fa-3x" id="loader" /> Please wait
...!
</React.Fragment>
) : (
<div className="chartContainer">
<Chart
options={this.state.ChartOne}
series={this.state.ChartOneSeries}
type="line"
width={400}
height={300}
/>
</div>
)}
</div>
</div>
<div id="my-signin2" />
</div>
<Footer />
</div>
);
}
}
export default Charts;

When you use setState you're triggering the lifecycle again. If you don't set your filterstartDate and filterendDate to "", you'll keep calling setState infinitely.
componentDidUpdate(prevProps, prevState) {
if (this.state.filterstartDate === "" && this.state.filterendDate === "") {
console.log("they are empty");
} else {
this.setState({
filterstartDate: "",
filterendDate: "",
test: "success!"
});
}
}

Related

How to update nested array and objects in react

I'm trying to update the state in react. However, still not clear how shall I do it. The nested structure is something like :
this.state ={
permissionsObject:{
fruitsGroup:[
{
index:0,
taste:false,
color:false,
},
{
index:1,
taste:false,
color:false,
},
],
fruitsGroup:[
{
index:0,
taste:false,
color:false,
},
{
index:1,
taste:false,
color:false,
},
]
}
}
Now, if I want to update the value of fruitsGroup -> taste based on the index value then how shall I proceed. I found there are various solutions exists for nested structure. However, none of them were answering my query or they are using react hooks while I'm not. Thanks well in advance.
Edit: The fruitGroup will have different name. So, how can I even locate it dynamically while setting the state.
You can do it like this:
import React, { Component } from "react";
export class App extends Component {
state = {
permissionsObject: {
fruitsGroup: [
{
index: 0,
taste: false,
color: false,
},
{
index: 1,
taste: false,
color: false,
},
],
groupName: [
{
index: 0,
taste: false,
color: false,
},
{
index: 1,
taste: false,
color: false,
},
],
},
};
updateStateHandler = (idx) => {
const updatedFruitsGroup = this.state.permissionsObject.fruitsGroup.map(
(item) => (item.index === idx ? { ...item, taste: true } : item)
);
const newState = {
permissionsObject: {
...this.state.permissionsObject,
fruitsGroup: updatedFruitsGroup,
},
};
this.setState(newState);
};
render() {
return (
<div>
{JSON.stringify(this.state)}
<button onClick={() => this.updateStateHandler(0)}>
Update index 0
</button>
<button onClick={() => this.updateStateHandler(1)}>
Update index 1
</button>
</div>
);
}
}
export default App;

Using react-bootstrap-table-next and react-bootstrap-table2-paginator to implement Jump To Page Button

I am newbie to React and trying to implement 'Jump To Page' functionality using Boot Strap Table with Pagination. Below is the code
import React, { Component} from 'react'
import BootstrapTable from 'react-bootstrap-table-next'
import 'react-bootstrap-table-next/dist/react-bootstrap-table2.min.css';
import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
import paginationFactory, { PaginationProvider,PaginationTotalStandalone} from 'react-bootstrap-table2-paginator';
class Product extends Component {
constructor() {
super();
this.onPageChange = this.onPageChange.bind(this);
this.handleOnchage = this.handleOnchage.bind(this);
this.handleClick = this.handleClick.bind(this);
this.state = {
data: [],
activePage: 1,
columns: [{
dataField: 'rowNumber',
text: 'Serial Number',
headerStyle: () => {
return { width: "10%" };
}
},
{
dataField: 'word',
text: 'Word',
sort: true,
headerStyle: () => {
return { width: "20%" };
}
}, {
dataField: 'count',
text: 'Count',
sort: true,
headerStyle: () => {
return { width: "10%" };
}
}, {
dataField: 'fileNames',
text: 'File Names',
headerStyle: () => {
return { textAlign: "left" };
}
}
]
}
}
handleOnchage(e) {
this.setState({activePage: e.target.value});
}
handleClick(e) {
e.preventDefault();
this.onPageChange(this.state.activePage);
}
onPageChange(page) {
console.log("in onPageChange page="+page);
if(page){
if(this.state.activePage !== page){
this.setState({activePage: page});
}
console.log("in onPageChange page="+page+" activePage="+this.state.activePage);
}
}
componentDidMount() {
fetch("http://10.0.0.18:8080/list-words")
.then(res => res.json())
.then(data => this.setState({ data }))
.catch(error => {
this.setState({
isLoaded: true,
error
});
})
}
render() {
const customTotal = (from, to, size) => (
<span className="react-bootstrap-table-pagination-total">
Showing { from } to { to } of { size } Results
</span>
);
const options = {
paginationSize: 25,
alwaysShowAllBtns: true,
firstPageText: 'First',
prePageText: 'Back',
nextPageText: 'Next',
lastPageText: 'Last',
nextPageTitle: 'First page',
prePageTitle: 'Pre page',
firstPageTitle: 'Next page',
lastPageTitle: 'Last page',
showTotal: true,
paginationTotalRenderer: customTotal,
totalSize: this.state.data.length,
onPageChange: this.onPageChange
};
const contentTable = ({ paginationProps, paginationTableProps }) => (
<div>
<div align="left">
<input name="activePage"
value={this.state.activePage}
onChange={this.handleOnchage} />
<button onClick={this.handleClick}>Jump to Page</button>
</div>
<ToolkitProvider
keyField="rowNumber"
data={this.state.data} selector
columns={this.state.columns}
search
loading={true}
>
{
toolkitprops => (
<div>
<div align="left"><SearchBar { ...toolkitprops.searchProps } />
<PaginationTotalStandalone { ...paginationProps }/></div><br/>
<BootstrapTable
striped
hover
{ ...toolkitprops.baseProps }
{ ...paginationTableProps }
/>
</div>
)
}
</ToolkitProvider>
</div>
);
return (
<PaginationProvider
pagination={
paginationFactory(options)
}
>
{ contentTable }
</PaginationProvider>
)
}
}
const { SearchBar } = Search;
export default Product;
When I enter a page number and click on the button, I see that onPageChange being called, but it doesn't render the boot strap table or jump to that Page. Rest of other functionality is working fine. I can search, I can click on pages to jump to them. Please let me know what am I missing.
"react": "^16.13.1",
"react-bootstrap-table": "^4.3.1",
"react-bootstrap-table-next": "^4.0.3",
"react-bootstrap-table2-filter": "^1.3.3",
"react-bootstrap-table2-toolkit": "^2.1.3",

custom pagination and filtering not working in react js

i want to do pagination with api ,when i fileter with with NO and when i want to go to next page within that filter its redirecting to default filter,below is my code
i want to do pagination with api ,when i fileter with with NO and when i want to go to next page within that filter its redirecting to default filter,below is my code
try {
let val = (typeof status[0] == "undefined" ? '' : status[0]);
if(val.toLowerCase() === 'yes'){
val = false;
} else {
val = true;
}
let res = await axios(`${process.env.REACT_APP_API_URL}messages?page=${page}&limit=10&limit=10&status=${val}`, {
headers: {
"x-access-token": cookies.get("token"),
"Content-Type": "application/json",
},
});
console.log(res,'test')
setData({ tableData: res.data.messages, isFetching: false });
setCurrentPage(res.data.currentpage);
} catch (err) {
console.log("############");
}
};
let opt = {
selectableRows: "none",
responsive: "scrollFullHeight",
serverSide: true,
count: count,
onTableChange: (action, tableState) => {
// a developer could react to change on an action basis or
// examine the state as a whole and do whatever they want
switch (action) {
case "changePage":
fetchData1(tableState.page,tableState.rowsPerPage);
break;
case "filterChange":
fetchData1(tableState.page,tableState.rowsPerPage,tableState.filterList[4]);
break;
}
}
}
<React.Fragment>
{data.isFetching ? (
<div className={classes.progressBarCont}>
<CircularProgress color="secondary" />
</div>
) : (
<React.Fragment>
<PageTitle title="List Of Messages" />
<Grid container spacing={3}>
<Grid item xs={12}>
<MUIDataTable
title="Messages List"
data={messageArr}
columns={[
{
name: "room",
label: "Room",
options: {
filter: true,
sort: true,
},
},
{
name: "sender",
label: "Sender",
options: {
filter: false,
},
},
{
name: "message",
label: "Message",
options: {
filter: false,
},
},
{
name: "sender",
label: "Sender",
options: {
filter: false,
},
},
{
name: 'unanswered',
label: 'Status',
options: {
filterType: 'dropdown',
customFilterListRender: value => `Status: ${value}`,
filterOptions: {
names: ["No","Yes"],
logic(unanswered, filters) {
}
},
},
},
]}
options={opt}
/>
</Grid>
</Grid>
</React.Fragment>
)}
<ToastContainer />
</React.Fragment> ```
First of you did not use page in you opt json for paging. you have to use page like:
let opt = {
selectableRows: "none",
responsive: "scrollFullHeight",
serverSide: true,
count: count,
page: page,
onTableChange: (action, tableState) => {
switch (action) {
case "changePage":
case "changeRowsPerPage":
changePage(tableState);
break;
case "search":
search(tableState);
break;
}
},
Then you should write two function changePage and search where you should call your api. but keep it mind that you must store correct tableState using searchText, filtering criteria and page information like pageNo, rowsPerPage and so on.

breaking big component into smaller one

I'm working on separating code from index.tsx into two different files viz: firstTab.tsx and secondTab.tsx. I haven't started working on secondTab.tsx yet.
I separated first tab related code into firstTab.tsx as shown in the following code editor: The full functional code with both tabs working are in index.tsx is pasted below:
import React, { Component } from "react";
import { render } from "react-dom";
import "jqwidgets-scripts/jqwidgets/styles/jqx.base.css";
import JqxButton from "jqwidgets-scripts/jqwidgets-react-tsx/jqxbuttons";
import * as ReactDOM from "react-dom";
import JqxWindow from "jqwidgets-scripts/jqwidgets-react-tsx/jqxwindow";
import JqxInput from "jqwidgets-scripts/jqwidgets-react-tsx/jqxinput";
import JqxChart, {
IChartProps
} from "jqwidgets-scripts/jqwidgets-react-tsx/jqxchart";
import JqxGrid, {
IGridProps,
jqx
} from "jqwidgets-scripts/jqwidgets-react-tsx/jqxgrid";
import JqxTabs from "jqwidgets-scripts/jqwidgets-react-tsx/jqxtabs";
import JqxDropDownList, {
IDropDownListProps
} from "jqwidgets-scripts/jqwidgets-react-tsx/jqxdropdownlist";
import firstTab from './firstTab';
interface AppProps {}
interface AppState {
name: string;
}
interface IProps extends IGridProps {
dropdownlistSource: IDropDownListProps["source"];
}
class App extends Component<{}, IProps> {
private myTabs = React.createRef<JqxTabs>();
private gridElement = React.createRef<HTMLDivElement>();
private myGrid = React.createRef<JqxGrid>();
private gridElementTwo = React.createRef<HTMLDivElement>();
private myGrid2 = React.createRef<JqxGrid>();
constructor(props: {}) {
super(props);
this.state = {
dropdownlistSource: [
{ value: 0, label: "Affogato" },
{ value: 1, label: "Americano" },
{ value: 2, label: "Bicerin" },
{ value: 3, label: "Breve" }
]
};
}
public render() {
return (
<JqxTabs
ref={this.myTabs}
// #ts-ignore
width={400}
height={560}
initTabContent={this.initWidgets}
>
<ul>
<li style={{ marginLeft: 30 }}>
<div style={{ height: 20, marginTop: 5 }}>
<div
style={{
marginLeft: 4,
verticalAlign: "middle",
textAlign: "center",
float: "left"
}}
>
US Indexes
</div>
</div>
</li>
<li>
<div style={{ height: 20, marginTop: 5 }}>
<div
style={{
marginLeft: 4,
verticalAlign: "middle",
textAlign: "center",
float: "left"
}}
>
NASDAQ compared to S&P 500
</div>
</div>
</li>
</ul>
<div style={{ overflow: "hidden" }}>
<div id="jqxGrid" ref={this.gridElement} />
<div style={{ marginTop: 10, height: "15%" }} />
</div>
<div style={{ overflow: "hidden" }}>
<div id="jqxGrid2" ref={this.gridElementTwo} />
<div style={{ marginTop: 10, height: "15%" }} />
</div>
</JqxTabs>
);
}
private initGrid = () => {
const source = {
datafields: [{ name: "Date" }, { name: "S&P 500" }, { name: "NASDAQ" }],
datatype: "csv",
localdata: `1/2/2014,1831.98,4143.07
1/3/2014,1831.37,4131.91
1/6/2014,1826.77,4113.68
1/7/2014,1837.88,4153.18
1/8/2014,1837.49,4165.61
1/9/2014,1838.13,4156.19
2/6/2014,1773.43,4057.12
2/7/2014,1797.02,4125.86`
};
const dataAdapter = new jqx.dataAdapter(source, {
async: false,
loadError: (xhr: any, status: any, error: any) => {
console.log(xhr, status, error);
}
});
const columns: IGridProps["columns"] = [
{ cellsformat: "d", datafield: "Date", text: "Date", width: 250 },
{ datafield: "S&P 500", text: "S&P 500", width: 150 },
{ datafield: "NASDAQ", text: "NASDAQ" }
];
const grid = (
<JqxGrid
ref={this.myGrid}
width={"100%"}
height={400}
source={dataAdapter}
columns={columns}
/>
);
render(grid, this.gridElement.current!);
};
private initGrid2 = () => {
const source = {
datafields: [{ name: "Date" }, { name: "S&P 500" }, { name: "NASDAQ" }],
datatype: "csv",
localdata: `1/2/2014,1831.98,4143.07
1/3/2014,1831.37,4131.91
1/6/2014,1826.77,4113.68
1/7/2014,1837.88,4153.18
1/8/2014,1837.49,4165.61
1/9/2014,1838.13,4156.19
1/10/2014,1842.37,4174.67
2/7/2014,1797.02,4125.86`
};
const dataAdapter = new jqx.dataAdapter(source, {
async: false,
loadError: (xhr: any, status: any, error: any) => {
console.log(xhr, status, error);
}
});
const columns: IGridProps["columns"] = [
{ cellsformat: "d", datafield: "Date", text: "Date", width: 250 },
{ datafield: "S&P 500", text: "S&P 500", width: 150 },
{ datafield: "NASDAQ", text: "NASDAQ" }
];
const grid = (
<JqxGrid
ref={this.myGrid2}
width={"100%"}
height={400}
source={dataAdapter}
columns={columns}
/>
);
render(grid, this.gridElementTwo.current!);
};
private initWidgets = (tab: any) => {
switch (tab) {
case 0:
this.initGrid();
break;
case 1:
this.initGrid2();
break;
}
};
}
render(<App />, document.getElementById("root"));
Question:
Since I've already moved private initGrid = () => { inside a separate file firstTab.tsx, in index.tsx where should I put {firstTab.tsx} to make sure both tabs in index.tsx works fine? I mean, even if I comment out private initGrid = () => { function from index.tsx both tabs should work fine.
Thanks
If I would refactor this I would consider the next approach:
Create Parent component Table (probably some more appropriate name)
Create a component for US Indexes
Create a component for NASDAQ compared to S&P 500
Based on the active tab render the proper component.
You could also create a separate file that contains only exports with your data.
If you then import that into your files with the functions you can use that there, keeps it cleaner.
And if you pass that data as a prop / param to your initGrid() functions, you don't have to repeat that code, can reuse it.

How to configure multiple bars on same chart using react-charts

I have a react wherein I am able to display the. For this i am using react-charts library.
To populate chart data I am making a API call and thereafter updating the state. I want to display multiple values for YAxis.
My sample API response is:
{
"result": 1,
"data": [
{
"data1": "1272.00",
"data2": "1183.00",
"price": "131.00"
},
{
"data1": "1328.00",
"data2": "1468.00",
"price": "132.00"
},
{
"data1": "1829.00",
"data2": "1445.00",
"price": "133.00"
},
]
}
I want data1 and data2 values forYAxis and price for XAxis.
React code:
import React, { Component } from "react";
import Sidebar from "./Sidebar";
import { Chart } from "react-charts";
import axios from "axios";
const qs = require("qs");
class Home extends Component {
state = {
datelist: [],
chart_data: []
};
componentDidMount() {
this.getDatesList();
axios
.post(
`http://127.0.0.1:8000/pricedata/`,
qs.stringify({ date: "2019-01-11" })
)
.then(res => {
if (res.data.result === 1) {
this.setState({
chart_data: [
{
label: "Strike",
data: res.data.data.map(Object.values)
}
]
});
} else {
this.setState({ chart_data: [] });
}
});
}
getDatesList() {
axios.get("http://127.0.0.1:8000/dateslist/").then(res => {
if (res.data.result === 1) {
this.setState({ datelist: res.data.data });
} else {
this.setState({ datelist: [] });
}
});
}
handleChange = event => {
var dateval = event.target.value;
axios
.post(`http://127.0.0.1:8000/pricedata/`, qs.stringify({ date: dateval }))
.then(res => {
if (res.data.result === 1) {
this.setState({
chart_data: [
{
label: "Strike",
data: res.data.data.map(Object.values)
}
]
});
} else {
this.setState({ chart_data: [] });
}
});
};
render() {
return (
<div className="container container_padding">
<div className="row">
<Sidebar />
<div className="col-md-9 col-sm-9 col-xs-12">
<select
className="form-control"
style={{ width: "120px", marginBottom: "10px" }}
onChange={this.handleChange}
>
{this.state.datelist.map((date, i) => (
<option value={date} key={i}>
{date}
</option>
))}
</select>
<div
style={{
width: "400px",
height: "300px"
}}
>
<Chart
data={this.state.chart_data}
series={{ type: "bar" }}
axes={[
{ primary: true, type: "ordinal", position: "bottom" },
{ type: "linear", position: "left", stacked: true }
]}
primaryCursor
tooltip
/>
</div>
</div>
</div>
</div>
);
}
}
export default Home;
How can I achieve this?
Thanks in advance.
Modify following lines:
handleChange = event => {
var dateval = event.target.value;
axios
.post(`http://127.0.0.1:8000/pricedata/`, qs.stringify({ date: dateval }))
.then(res => {
if (res.data.result === 1) {
this.setState({
chart_data: [
{
label: "bar1",
data: res.data.data.map((d) => ({ x: d.price, y: d.data1 })
},
{
label: "bar2",
data: res.data.data.map((d) => ({ x: d.price, y: d.data2 })
}
]
});
} else {
this.setState({ chart_data: [] });
}
});
};
and inside render:
<Chart
data={this.state.chart_data}
series={{ type: 'bar' }}
axes={[
{ primary: true, position: 'bottom', type: 'ordinal' },
{ position: 'left', type: 'linear', min: 0 },
]}
primaryCursor
secondaryCursor
tooltip
/>

Resources