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

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
/>

Related

Rfresh the parents component while updating in detailCellRendering in ag-grid

We have One parents componet Template in That i m using one detailsCellRendering componet for cellRedering in ag-grid..in the detailsCellRendering componet I am updating TextValue and after clicking on handleDoneValue event (done button ) the Template page should also refresh.But I am not able to do ...i need help like how can i update Template page while updating in DetailsCellRending Component.
class Template extends Component {
constructor(props) {
super(props);
this.state = {
columnDefs: [
{
headerName: "Channel Field",
field: "field",
filter: "agTextColumnFilter",
filterParams: {
buttons: ["reset"],
debounceMs: 1000,
suppressAndOrCondition: true, // it will remove AND/OR conditions
filterOptions: [
"contains",
"notContains",
"equals",
"notEqual",
"startsWith",
"endsWith"
]
},
cellRendererParams: {
prop1: "this is prop1"
},
cellRenderer: "agGroupCellRenderer",
},
{
headerName: "Mapping from Your Data",
field: "mapping_field",
filter: "agNumberColumnFilter",
},
{
headerName: skuValue,
field: "value",
},
{
headerName: "Status",
field: "status",
filter: 'agSetColumnFilter',
filterParams: {
values: function (params) {
// simulating async delay
setTimeout(function () {
params.success(["True", "False"]);
}, 500);
}
},
// cellRenderer: "ColourRender",
cellStyle: params => {
if (params.value === "Mapped") {
//mark mapped cells as green
return { color: 'green' };
} else {
return { color: 'red' };
}
}
},
{
headerName: "Required",
field: "required",
filter: "agSetColumnFilter",
cellRenderer: "BooleanRender",
filterParams: {
values: function (params) {
// simulating async delay
setTimeout(function () {
params.success(["True", "False"]);
}, 500);
}
}
},
],
columnDefsforSku: [
{
headerName: "Channel Field",
field: "field",
filter: "agTextColumnFilter",
filterParams: {
buttons: ["reset"],
debounceMs: 1000,
suppressAndOrCondition: true, // it will remove AND/OR conditions
filterOptions: [
"contains",
"notContains",
"equals",
"notEqual",
"startsWith",
"endsWith",
// "In List",
// "Not In List"
]
},
cellRenderer: "agGroupCellRenderer",
},
{
headerName: "Mapping from Your Data",
field: "mapping_field",
filter: "agNumberColumnFilter",
},
{
headerName: "Status",
field: "status",
filter: 'agSetColumnFilter',
filterParams: {
values: function (params) {
// simulating async delay
setTimeout(function () {
params.success(["True", "False"]);
}, 500);
}
},
cellStyle: params => {
if (params.value === "Mapped") {
//mark mapped cells as green
return { color: 'green' };
} else {
return { color: 'red' };
}
}
},
{
headerName: "Required",
field: "required",
filter: "agSetColumnFilter",
cellRenderer: "BooleanRender",
filterParams: {
values: function (params) {
// simulating async delay
setTimeout(function () {
params.success(["True", "False"]);
}, 500);
}
}
},
],
components: {
DetailCellRenderer: {DetailCellRenderer},
},
defaultColDef: {
flex: 1,
resizable: true,
// tooltipComponent: "customTooltip",
floatingFilter: true,
minWidth: 170,
sortable: true,
},
rowModelType: "serverSide",
frameworkComponents: {
customNoRowsOverlay: CustomNoRowsOverlay,
loadingRender: loadingRender,
myDetailCellRenderer: DetailCellRenderer,
customTooltip: CustomTooltip,
BooleanRender: BooleanRender,
// ColourRender: ColourRender,
},
context: { componentParent: this },
pagination: true,
serverSideStoreType: "full",
rowSelection: "multiple",
loadingCellRenderer: "loadingRender",
loadingCellRendererParams: { loadingMessage: "One moment please..." },
loadingOverlayComponent: "loadingRender",
totalProductsCount: 0,
catalogfilterCount: 0,
noRowsOverlayComponent: "customNoRowsOverlay",
noRowsOverlayComponentParams: {
noRowsMessageFunc: function () {
return "Please Select Product Preview"
}
},
getDetailRowData: function (params) {
// params.successCallback([]);
params.successCallback(params.data.callRecords);
},
sideBar: 'filters',
tooltipShowDelay: 0,
paginationPageSize: 100,
cacheBlockSize: 30,
filterStatus: false,
sweetAlert: "",
allSKu: [],
sku: localStorage.getItem("skuPreviewProduct"),
isProductPrevewSelected: false,
skuInputValue: "",
loading: false,
product_id: localStorage.getItem("products_ids") ? localStorage.getItem("products_ids") : null,
// loadingForDetails: localStorage.getItem("loadings")
};
<Grid item xs={10}>
<div
id="myGrid"
style={{
height: "90%",
width: "90%",
}}
className="ag-theme-alpine template"
>
<AgGridReact
columnDefs={this.state.product_id ? columnDefs : columnDefsforSku}
defaultColDef={defaultColDef}
onGridReady={this.onGridReady}
rowModelType={rowModelType}
masterDetail={true}
enableCellChangeFlash={true}
masterDetail={true}
detailCellRenderer={'myDetailCellRenderer'}
detailRowHeight={350}
height={400}
width={600}
animateRows={true}
frameworkComponents={frameworkComponents}
embedFullWidthRows={true}
loadingOverlayComponent={loadingOverlayComponent}
loadingCellRenderer={loadingCellRenderer} // default loading renderer
loadingCellRendererParams={loadingCellRendererParams} // renderer Params to load msg
noRowsOverlayComponent={noRowsOverlayComponent} // default no rows overlay component
noRowsOverlayComponentParams={noRowsOverlayComponentParams} // to show default no rows message
paginationPageSize={this.state.paginationPageSize} // pagination page size
onColumnVisible={this.onColumnVisible.bind(this)}
getDetailRowData={getDetailRowData}
cacheBlockSize={cacheBlockSize}
detailCellRendererParams={detailCellRendererParams }
/>
</div>
</Grid>
</Grid>
</div>
)
1. **List item**
}
// DetailsCellRendering page
import React, { useEffect, useState } from 'react';
import Paper from '#material-ui/core/Paper';
import Grid from '#material-ui/core/Grid';
import { makeStyles } from '#material-ui/core/styles';
import TemplateTab from "../views/FunctionalComponents/TemplateTab"
import { TEMPLATE_MARKETPLACE_FEILD_MAPPING, TEMPLATE_API } from "../configurations/configApi";
import { apiEdit, fetchUrl } from "../apiActions/action";
import LoadingOverlay from "../components/overlays/LoadingOverlay";
import Templates from 'views/Tables/Templates';
// import Template from 'views/Tables/Templates';
const useStyles = makeStyles(() => ({
root: {
flexGrow: 1,
textAlign: "center",
marginBottom: 20,
}
}));
const DetailCellRenderer = ({ data, params, api, node}) => {
const classes = useStyles();
const [allData, setAllData] = useState();
const [loading, setLoading] = useState(false);
const marketplaceId = localStorage.getItem("marketplaceId") ? localStorage.getItem("marketplaceId") : "" ;
const skuValue = localStorage.getItem("skuPreviewProduct") ? localStorage.getItem("skuPreviewProduct") : "";
const fetchAllTemplate = () => {
setLoading(true);
var soniServe = `${TEMPLATE_API}${marketplaceId}/?sku=${skuValue}&filter=${"[]"}`;
fetchUrl(soniServe, ({ status, response }) => {
if (status) {
let refreshValue = response.data.response[0].mappings.filter(item => data.id === item.id);
localStorage.setItem("cellRenderId", refreshValue && refreshValue[0]&& refreshValue[0].id )
setAllData(refreshValue && refreshValue[0])
setLoading(false)
} else {
if (response.response &&
response.response.status &&
((response.response.status === 401))
) {
localStorage.removeItem("token");
localStorage.removeItem("email");
window.location = "/auth/login-page";
}
}
});
}
useEffect(() => {
setAllData(data)
}, data)
// update text value
const handleDoneValue = (textValue) => {
const productId = allData && allData.id;
let value = textValue
// updating the existing labels
const urlForSave = `${TEMPLATE_MARKETPLACE_FEILD_MAPPING}${productId}/`;
const methodOfSave = "put";
const data = { "mappings": { "text_value": value } }
apiEdit(urlForSave, data, methodOfSave, ({ status, response }) => {
if (status) {
// window.location = `/configuration/details/${marketplaceId}`
fetchAllTemplate();
// inputMethod === "saveAs" && this.fetchProductDisplay("saveAs");
} else {
if (response.response &&
response.response.status &&
((response.response.status === 401))
) {
localStorage.removeItem("token");
localStorage.removeItem("email");
window.location = "/auth/login-page";
} else if (
(response &&
response.status &&
(response.status === 404 ||
response.status === 500)) ||
!response
) {
window.location = "/error";
} else {
// to show some popups
window.location = "/error";
}
}
});
}
return (
<div>
<div className={classes.root}>
<Grid container style={{ paddingTop: "10px" }}>
<Grid item xs={3}>
{allData && allData.field ? allData.field : null}
</Grid>
<Grid item xs={3}>
{ allData && allData.mapping_field ? allData.mapping_field : null}
</Grid>
<Grid item xs={3}>
{urlCheck && urlCheck[0] === "https" || urlCheck && urlCheck[0] === "http"? {allData && allData.value} : allData && allData.value ? allData.value : null}
{/* { data && data.value ? data.value : null} */}
</Grid>
<Grid item xs={3} >
{allData && allData.status}
</Grid>
</Grid>
</div>
<div className={classes.root}>
<Grid container spacing={1}>
<Grid item xs={4} justify="flex-end" style={{ display: "flex" }}>
Description
</Grid>
<Grid item xs={8} >
<Paper className="templateBox">
<span style={{ marginBottom: "20px" }}>What value should we send for each product's "{data.field}"? </span>
<TemplateTab allCellData={data} handleDoneValue={handleDoneValue}
/>
</Paper>
</Grid>
</Grid>
</div>
<LoadingOverlay showOverlay={loading} />
</div>
);
};
export default DetailCellRenderer;

React Data Grid: Custom DropDown Editor: value is not getting updated. Grid is not getting enabled for editing

on react-data-grid 7.0.0-beta
I read through the most recent demos provided in git repo for react-data-grid and implemented a custom dropdown for my use case.
Dropdown seems to be working but it is not updating the grid data upon selection. The editable property doesn't seem to be working either.
test code is implemented here:
Sandbox: https://codesandbox.io/s/react-data-grid-custom-dropdown-editor-kcy5n
export const EntryCriteriaGrid = () => {
const columns = [
{
key: "r1",
name: "Criteria",
width: "50%",
resizable: true,
editable: true
},
{
key: "status",
name: "Status",
editor: DropdownCustomEditor,
editorOptions: {
editOnClick: true
},
editable: true
},
{ key: "tracker", name: "Tracker", editable: true }
];
const rows = [
{ r1: "data 1", status: "BLOCKED", tracker: "tracker 1" },
{ r1: "data 2", status: "PASS", tracker: "tracker 1" },
{ r1: "data 3", status: "ISSUE", tracker: "tracker 2" }
];
const [state, setState] = useState({ rows });
const onGridRowsUpdated = ({ fromRow, toRow, updated }) => {
setState((state) => {
const rows = state.rows.slice();
for (let i = fromRow; i <= toRow; i++) {
rows[i] = { ...rows[i], ...updated };
}
return { rows };
});
};
return (
<div>
<ReactDataGrid
columns={columns}
rows={state.rows}
rowsCount={3}
onGridRowsUpdated={onGridRowsUpdated}
enableCellSelect={true}
className="rdg-light"
/>
</div>
);
};
export default EntryCriteriaGrid;
import React, { Component } from "react";
import ReactDOM from "react-dom";
export default class DropdownCustomEditor extends Component {
constructor(props) {
super(props);
this.state = {
selected: ""
};
this.options = [
{ id: "blocked", value: "BLOCKED" },
{ id: "pass", value: "PASS" },
{ id: "issue", value: "ISSUE" },
{ id: "notStarted", value: "NOT STARTED" }
];
}
componentDidMount() {
if (this.props.row && this.props.row.status)
this.setState({ selected: this.props.row.status });
}
getValue = function () {
return { status: this.state.selected };
};
getInputNode() {
return ReactDOM.findDOMNode(this).getElementsByTagName("select")[0];
}
update(e) {
this.setState({ selected: e.target.value });
this.props.onRowChange({ ...this.props.row, status: e.target.value }, true);
}
render() {
return (
<select
className="rdg-select-editor"
onChange={(e) => this.update(e)}
autoFocus
value={this.state.selected}
>
{this.options.map((elem) => {
return (
<option key={elem.id} value={elem.value}>
{elem.value}
</option>
);
})}
</select>
);
}
}
Just change your code as follows:
In DropdownCustomEditor component:
update(e) {
this.setState({ selected: e.target.value });
this.props.onRowChange({ ...this.props.row, status: e.target.value });
}
In EntryCriteriaGrid component
const onGridRowsUpdated = (rows) => {
setState({ rows });
};
and
<ReactDataGrid
columns={columns}
rows={state.rows}
rowsCount={3}
//onRowsUpdate={onGridRowsUpdated}
enableCellSelect={true}
className="rdg-light"
onRowsChange={(rows) => onGridRowsUpdated(rows)}
/>

React API Call, can't assign to pass as props to other components

I wanted to see if I could get some help on HOW to call data points in this api call, it is an array of numbers, so a field value might be 6. But I can't ever get anything to load on screen. My call is working as I'm getting the Loading... when null, but then it just disappears and doesn't display anything. Whenever I try to assign a Number to data, it says unrecognized.
import React, { Component } from 'react'
let headers = {
'QB-Realm-Hostname': 'XXXXXXXXXXXXXX.quickbase.com',
'User-Agent': 'FileService_Integration_V2.1',
'Authorization': 'QB-USER-TOKEN XXX_XXXX_XXXXXXXXXXXXXXXXXXX',
'Content-Type': 'application/json'
};
class JobsTableApi extends Component {
state = {
data: null,
}
componentDidMount() {
this.fetchData();
}
fetchData = () => {
let body = {"from":"bpz99ram7","select":[3,6,80,81,82,83,86,84,88,89,90,91,92,93,94,95,96,97,98,99,101,103,104,105,106,107,109,111,113,115,120,123,224,225,226,227,228,229,230,231,477,479,480,481],"sortBy":[{"fieldId":6,"order":"ASC"}],"groupBy":[{"fieldId":40,"grouping":"equal-values"}],"options":{"skip":0,"top":0,"compareWithAppLocalTime":false}}
fetch('https://api.quickbase.com/v1/records/query', {
method: 'POST',
headers: headers,
body: JSON.stringify(body)
}).then(response => {
if (response.ok) {
return response.json().then(res => {
this.setState({
data: [],
})
});
}
return response.json().then(resBody => Promise.reject({status: response.status, ...resBody}));
}).catch(err => console.log(err))
}
render() {
const { data } = this.state;
if (data === null) return 'Loading...';
return (
<div>{data["3"]}</div>
)
}
}
export default JobsTableApi;
API data Ex:
{
"data": [
{
"3": {
"value": 43
},
"18": {
"value": "Radiant"
},
"20": {
"value": null
},
"144": {
"value": null
},
"145": {
"value": 33230
},
"171": {
"value": 8
},
"172": {
"value": 228
},
"174": {
"value": 270
},
"212": {
"value": 0
},
"215": {
"value": 8.34487776140499
},
"216": {
"value": 16.34487776140499
},
"217": {
"value": 244.344877761405
},
"218": {
"value": 572.3449342166289
},
"219": {
"value": 842.3449342166289
},
"220": {
"value": 861.8163156599072
},
"221": {
"value": 877.8106647026001
},
"222": {
"value": 0
},
"223": {
"value": 239.256
},
"227": {
"value": 5050.96
},
"230": {
"value": 239.256
},
"231": {
"value": 239.256
},
"232": {
"value": 17339.414
},
"233": {
"value": 26743.504
},
"234": {
"value": 22390.374
},
"235": {
"value": 22948.638
},
"236": {
"value": 23407.212
},
"244": {
"value": 0
},
"249": {
"value": 0
},
"322": {
"value": 870.6260000000001
},
"325": {
"value": 17100.158
},
"338": {
"value": ""
},
"349": {
"value": 8
},
"350": {
"value": 0
},
"351": {
"value": 220
},
"366": {
"value": 0
},
"438": {
"value": null
},
"513": {
"value": 278
},
"516": {
"value": 23261
},
"517": {
"value": 17339.414
}
}
UPDATE: Able to set the {jobId} as a prop in my App.js, since this is the parent to my Line Charts, this is where the truth needs to be, then I can send this over to each line chart to pull based on which Title (and therefore ID) is displaying which dictates the result of the API call.
App.js
import React, { useEffect, useState } from "react";
import './App.css'
import Title from './components/header/Title'
import TotalLineChart from './components/charts/TotalLineChart'
import RadiantLineChart from './components/charts/RadiantLineChart'
import PlumbingLineChart from './components/charts/PlumbingLineChart'
import SnowmeltLineChart from './components/charts/SnowmeltLineChart'
import HVACLineChart from './components/charts/HVACLineChart'
import GasPipeLineChart from './components/charts/GasPipeLineChart'
import FixturesLineChart from './components/charts/FixturesLineChart'
// import TitleCycle from './components/TitleCycle'
// import Logo from './components/Logo';
let headers = {
"QB-Realm-Hostname": "XXXXXX.quickbase.com",
"User-Agent": "FileService_Integration_V2.1",
"Authorization": "QB-USER-TOKEN XXXXXXX",
"Content-Type": "application/json",
"Retry-After": 120000
};
function App() {
const [allData, setAllData] = useState([]);
const [index, setIndex] = useState(0);
// Fetch all data, all jobs
useEffect(() => {
function fetchData() {
let body = {
from: "bpz99ram7",
select: [3, 6, 40],
where: "{40.CT. 'In Progress'}",
sortBy: [{ fieldId: 6, order: "ASC" }],
groupBy: [{ fieldId: 40, grouping: "equal-values" }],
options: { skip: 0, top: 0, compareWithAppLocalTime: false },
};
fetch("https://api.quickbase.com/v1/records/query", {
method: "POST",
headers: headers,
body: JSON.stringify(body),
})
.then((response) => response.json())
.then(({ data }) => setAllData(data));
}
fetchData();
}, []);
// Cycle through the jobIds and indexes
useEffect(() => {
const timerId = setInterval(
() => setIndex((i) => (i + 1) % allData.length),
5000 // 5 seconds.
);
return () => clearInterval(timerId);
}, [allData]);
// console.log(allData)
// console.log(index)
// Calculate info based on index
const jobId = allData[index]?.['3']?.value || '291'; // Default 291
const title = allData[index]?.['6']?.value || 'Default Title';
// console.log(jobId)
return (
<div>
{/* <div className="flexbox-container">
<div className="Logo">
{/* <Logo /> */}
{/* </div> */}
<div className="App">
<Title title = {title} />
</div>
<div className="TopChart">
<TotalLineChart jobId = {jobId} />
</div>
<div className="FirstRowContainer">
{/* <RadiantLineChart jobId = {jobId} /> */}
<PlumbingLineChart jobId = {jobId} />
<FixturesLineChart jobId = {jobId} />
</div>
<div className="SecondRowContainer">
<SnowmeltLineChart jobId = {jobId} />
<HVACLineChart jobId = {jobId} />
<GasPipeLineChart jobId = {jobId} />
</div>
</div>
);
}
export default App;
LineChart.js
import React, { useState, useEffect } from "react";
import { Scatter } from "react-chartjs-2";
// import jobId from '../TitleCycle';
// import Title from '../header/Title';
function TotalLineChart(props) {
const { jobId } = props;
// console.log(`${jobId}`)
const [chartData, setChartData] = useState({});
const chart = () => {
let designHours = [];
let designAmount = [];
let subRoughHours = [];
let subRoughAmount = [];
let roughHours = [];
let roughAmount = [];
let finishHours = [];
let finishAmount = [];
let closeHours = [];
let closeAmount = [];
let actualHours = [];
let actualAmount = [];
let headers = {
"QB-Realm-Hostname": "XXXXXXXXXX.quickbase.com",
"User-Agent": "FileService_Integration_V2.1",
"Authorization": "QB-USER-TOKEN XXXXXXXXXXX",
"Content-Type": "application/json",
"x-ratelimit-reset": 10000,
"Retry-After": 30000
};
// useEffect(() => {
// function fetchData() {
const body = {
from: "bpz99ram7",
select: [
3,
88,
91,
92,
95,
96,
98,
104,
107,
224,
477,
479,
480,
],
where: `{3.EX. ${ jobId }}`,
sortBy: [{ fieldId: 6, order: "ASC" }],
groupBy: [{ fieldId: 40, grouping: "equal-values" }],
options: { skip: 0, compareWithAppLocalTime: false }
};
fetch("https://api.quickbase.com/v1/records/query", {
method: "POST",
headers: headers,
body: JSON.stringify(body)
})
// }
// fetchData();
// }, [])
.then((response) => response.json())
.then((res) => {
// console.log(res);
Object.keys(res.data).map(jobId => {
designHours = parseInt(res.data[jobId]['88'].value, 10);
designAmount = parseInt(res.data[jobId]['91'].value, 10);
subRoughHours = parseInt(res.data[jobId]['92'].value, 10);
subRoughAmount = parseInt(res.data[jobId]['95'].value, 10);
roughHours = parseInt(res.data[jobId]['96'].value, 10);
roughAmount = parseInt(res.data[jobId]['98'].value, 10);
finishHours = parseInt(res.data[jobId]['104'].value, 10);
finishAmount = parseInt(res.data[jobId]['107'].value, 10);
closeHours = parseInt(res.data[jobId]['477'].value, 10);
closeAmount = parseInt(res.data[jobId]['480'].value, 10);
actualHours = parseInt(res.data[jobId]['479'].value, 10);
actualAmount = parseInt(res.data[jobId]['224'].value, 10);
setChartData({
type: 'scatter',
redraw: true,
datasets: [
{
label: 'TOTAL',
data: [
{ x: designHours, y: designAmount },
{ x: subRoughHours, y: subRoughAmount },
{ x: roughHours, y: roughAmount },
{ x: finishHours, y: finishAmount },
{ x: closeHours, y: closeAmount }
],
borderWidth: 2,
borderColor: '#4183c4',
backgroundColor: '#4183c4',
tension: 0.8,
spanGaps: true,
lineTension: 0.5,
showLine: true,
fill: false,
showTooltip: false,
pointBorderWidth: 1
},
{
label: 'ACTUALS',
data: [{ x: actualHours, y: actualAmount }],
fill: false,
borderColor: '#e34747',
backgroundColor: '#e34747',
borderWidth: 3,
showTooltip: false
}
],
options: {
showAllTooltips: true,
enabled: true,
maintainAspectRatio: false,
legend: {
display: true
}
}
})
})
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
chart();
}, []);
return (
<div className="App">
<div>
<Scatter
// ref={(reference) => this.chartReference = reference }
data={chartData}
options={{
title: { text: "Total Project", display: false },
scales: {
yAxes: [
{
scaleLabel: {
display: true,
labelString: '$ AMOUNT'
},
ticks: {
autoSkip: true,
maxTicksLimit: 10,
beginAtZero: true
},
gridLines: {
display: true
}
}
],
xAxes: [
{
scaleLabel: {
display: true,
labelString: 'HOURS'
},
gridLines: {
display: true
}
}
],
},
}}
/>
</div>
</div>
);
};
export default TotalLineChart;
As you can see, the API is getting the '#' id of whatever {jobId} is coming from App.js. Which you can also see in my API body.

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",

ComponentDidUpdate causing infinite render even when wrapped in condition

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!"
});
}
}

Resources