I'm using async useEffect in React because I need to do database requests. Then, add this data to my react-charts-2
const [ plSnapShot, setPlSnapShot ] = useState({
grossIncome: 0.00,
opeExpenses: 0.00,
nonOpeExpenses: 0.00,
netIncome: 0.00,
grossPotencialRent: 0.00,
lastMonthIncome: 0.00
});
const [ thisMonthPayment, setThisMonthPayments ] = useState({
labels: [],
data: [],
color: 'blue'
});
useEffect(() => {
async function fetchData() {
await axios.get(`${url.REQ_URL}/home/getUserFullName/${userID}`)
.then(async (res) => {
setUserFullName(res.data);
await axios.get(`${url.REQ_URL}/home/getThisMonthPayments/${propertyID}`)
.then(async (resMonthPay) => {
let total = 0;
let obj = {
labels: [],
data: [],
color: 'blue'
};
const data = resMonthPay.data;
for(const d of data) {
obj.labels.push(helper.formatDate(new Date(d.date)));
obj.data.push(d.amount);
total += d.amount;
}
setThisMonthPayments(obj);
setTotalEarnedMonth(parseFloat(total));
await axios.get(`${url.REQ_URL}/home/plSnapshot/${propertyID}`)
.then(async (resPL) => {
const data = resPL.data;
setPlSnapShot({
grossIncome: parseFloat(data.GrossIncome || 0).toFixed(2),
opeExpenses: parseFloat(data.OperatingExpenses || 0).toFixed(2),
nonOpeExpenses: parseFloat(data.NonOperatingExpenses || 0).toFixed(2),
netIncome: parseFloat(data.NetIncome || 0).toFixed(2),
grossPotencialRent: parseFloat(data.GrossPotencialRent || 0).toFixed(2),
lastMonthIncome: parseFloat(data.LastMonthIncome || 0).toFixed(2)
});
});
});
});
}
fetchData();
}, [propertyID, userID]);
const pieChart = {
chartData: {
labels: ['Gross Income', 'Operating Expenses', 'Non Operating Expenses'],
datasets: [{
data: [plSnapShot.grossIncome, plSnapShot.opeExpenses, plSnapShot.nonOpeExpenses],
backgroundColor: [
ChartConfig.color.primary,
ChartConfig.color.warning,
ChartConfig.color.info
],
hoverBackgroundColor: [
ChartConfig.color.primary,
ChartConfig.color.warning,
ChartConfig.color.info
]
}]
}
};
const horizontalChart = {
label: 'Last Month Income',
labels: ['Gross Potencial Rent', 'Last Month Income'],
chartdata: [plSnapShot.grossPotencialRent, plSnapShot.lastMonthIncome]
};
Here is an example of how I call the Chart component in my code in the render/return method.
<TinyPieChart
labels={pieChart.chartData.labels}
datasets={pieChart.chartData.datasets}
height={110}
width={100}
/>
And my Pie Chart component is just to display it
import React from 'react';
import { Pie } from 'react-chartjs-2';
// chart congig
import ChartConfig from '../../Constants/chart-config';
const options = {
legend: {
display: false,
labels: {
fontColor: ChartConfig.legendFontColor
}
}
};
const TinyPieChart = ({ labels, datasets, width, height }) => {
const data = {
labels,
datasets
};
return (
<Pie height={height} width={width} data={data} options={options} />
);
}
export default TinyPieChart;
Mostly of the times it works just fine, but sometimes the chart data is loaded and displayed in the screen real quick, then it disappear and the chart is displayed empty (no data). Am I loading it properly with the useEffect or should I use another method?
Thanks you.
The momentary flashing is likely due to the fact that the chart data is empty on first render. So depending on the time it take for your useEffect to fetch the data, that flashing may present a real problem.
One common solution is to use a state variable to indicate that the data is being loaded and either not display anything in place of the chart or display a loaded of some sort. So you can add something like you suggested in the comments const [ loader, setLoader ] = useState(true). Then once the data is loaded, togged it to false.
Meanwhile, inside your render function, you would do:
...
...
{loader ?
<div>Loading....</div>
:
<TinyPieChart
labels={pieChart.chartData.labels}
datasets={pieChart.chartData.datasets}
height={110}
width={100}
/>
}
loader can go from true to false or vise versa, depending on what make more intuitive sense to you.
Related
I am working on a digital currency personal project.
My problem is that when I click on any digital currency to show the details of that coin,
The new page must be refreshed to display the chart correctly, or use a tag,
but I use the Link (react-router-dom),
And before refreshing the page, my chart is shown as below.
(https://ibb.co/NFgNMmX)
And after refreshing the page, my chart is shown as below.
(https://ibb.co/DzWRXRY)
and this is my code
const CoinChart = () => {
let id = useParams();
// state
const [chartDay, setChartDay] = useState({
value: "1",
text: "1d"
})
// redux
const dispatch = useDispatch<any>();
const detail = useSelector((state: State) => state.coin_detail.chart);
useEffect(() => {
dispatch(coinChartFetchRequestFunc(id.coin_id, chartDay.value));
}, [chartDay]);
// chart config
const labels = detail.chart?.prices.map((item) => {
if (chartDay.value == "max") {
return (
new Date(item[0]).toLocaleDateString()
)
}
else {
return (
new Date(item[0]).getDate() +
"." +
new Date(item[0]).toDateString().split(/[0-9]/)[0].split(" ")[1] +
" " +
new Date(item[0]).getHours() +
":" +
new Date(item[0]).getMinutes()
);
}
});
const Data = {
labels,
datasets: [
{
fill: true,
drawActiveElementsOnTop: false,
data: detail.chart?.prices.map((item) => {
return item[1];
}),
label: "price(usd)",
borderColor: "#3861fb",
backgroundColor: "#3861fb10",
pointBorderWidth: 0,
borderWidth: 2.5,
},
],
};
return (
//some code
<Line data={Data} />
);
};
Is this problem solvable?
Also, I used translate in some part, and if there are any problems in the above texts, excuse me!
I am dynamically changing the values that are displayed on the areachart. But for some reason the chart is only displayed if I change one of the dynamic variable with a hard coded number in the array. For example
const data = [
["Year", "Free Cash Flow", "Revenue"],
[this.props.date1, this.props.cashFlow1, this.props.revenue1],
[this.props.date2, this.props.cashFlow2, this.props.revenue2],
[this.props.date3, this.props.cashFlow3, this.props.revenue3],
[this.props.date4, this.props.cashFlow4, this.props.revenue4],
[this.props.date5, this.props.cashFlow5, this.props.revenue5],
];
that is how I structured my data array, but it doesn't renders and give the following error All series on a given axis must be of the same data type. However, if I replace this.props.revenue1 with let say 100 the area chart renders
const data = [
["Year", "Free Cash Flow", "Revenue"],
[this.props.date1, this.props.cashFlow1, 100],
[this.props.date2, this.props.cashFlow2, this.props.revenue2],
[this.props.date3, this.props.cashFlow3, this.props.revenue3],
[this.props.date4, this.props.cashFlow4, this.props.revenue4],
[this.props.date5, this.props.cashFlow5, this.props.revenue5],
];
I have looked at other examples and I can't seem to find a mistake I could've made.
import React, {Component} from "react";
import { Chart } from "react-google-charts";
class AreaChart extends Component {
render () {
const chartEvents = [
{
callback: ({ chartWrapper, google }) => {
const chart = chartWrapper.getChart();
chart.container.addEventListener("click", (ev) => console.log(ev))
},
eventName: "ready"
}
];
const rev1 = this.props.revenue1;
const FCF1 = this.props.cashFlow1;
const data = [
["Year", "Free Cash Flow", "Revenue"],
[this.props.date1, this.props.cashFlow1, this.props.revenue1],
[this.props.date2, this.props.cashFlow2, this.props.revenue2],
[this.props.date3, this.props.cashFlow3, this.props.revenue3],
[this.props.date4, this.props.cashFlow4, this.props.revenue4],
[this.props.date5, this.props.cashFlow5, this.props.revenue5],
];
const options = {
isStacked: true,
height: 300,
legend: { position: "top", maxLines: 3 },
vAxis: { minValue: 0 },
};
return (
<Chart
chartType="AreaChart"
width="75%"
height="400px"
data={data}
options={options}
chartEvents={chartEvents}
/>
);
}
}
export default AreaChart;
Background
I have an chart which displays static data just fine.
Using this template https://github.com/creativetimofficial/black-dashboard-react/blob/master/src/variables/charts.js
On my main page [dash.js] I have an API call, (which I tested presents the data I expect by using console.log().
I will be looking to have this data working dynamically so I have created it using useEffect and useState.
For reference;
const [chrtState, setChrtState] = useState({
loading: false,
chartos: null,
});
useEffect(() => {
setChrtState({loading: true});
const apiUrl = `http://example.com/api/request/`;
axios
.get(apiUrl, {
withCredentials: true,
})
.then(res => {
setChrtState({loading: false, repos: res.data.characters});
});
}, [setChrtState]);
const setCanvas = name => {
const apiUrl = `http://example.com/api/request/`;
axios
.get(apiUrl, {
withCredentials: true,
})
.then(res => {
setChrtState({loading: false, chartos: res.data.characters});
//console.log(res.data.characters);
});
};
return (
<Line
data={chartExample1[bigChartData + bigChartTime]}
options={chartExample1.options}
apiprops={chrtState.chartos}
/>
);
Note: the data parameter is used to select a specific chart-type (e.g. data1, data2, etc), this part works fine and isn't related to the APIdata as such.
My Problem
I am struggling to work out how to pass the API data to the chart.js
I tried using some other examples of how to pass props but it is proving very confusing for me given that it is already passing data1: (canvas) etc.
What I've tried
I tried to add an additional parameter before data1 (line 77) in charts.js, as follows;
apiprops: (props) => {
const {repos} = props;
console.log(repos);
},
but nothing was printed to the console for this.
I tried adding the data to canvas but this is already passing information used to render the height, width and style of the of the chart.
I have tried to add the API to the charts.js file, however when I add import axios from 'axios'; to the top of this page it throws out a syntax error. But I think it makes more sense to pull the API elsewhere and pass as a prop anyway. (please let me know if you disagree).
I am very much still building my knowledge of reactjs so thank you for any help and guidance on this!
End goal
For reference, my end goal will be to pass the API data to the chart and then process each dictionary into the labels and the datasets.data - the API passes in this order
{
"characters": [
{
"label": 123,
"data": 321
},
{
"label": 456,
"data": 654
}
]
}
I understood that you are trying to inject your API values into the existing functions in charts.js. First, you need to separate the API values into two arrays: labels and data. You can do that with reduce
const values = res.data.characters.reduce(
(acc, character) => ({
labels: [...acc.labels, character.label],
data: [...acc.data, character.data],
}),
{ labels: [], data: [] }
);
setChrtState({ loading: false, repos: values });
To inject them into the functions, you'll need to modify the functions a little using currying
data1: ({labels, data}) => (canvas) => {
...
return {
labels,
datasets: [
{
...
data,
},
],
};
},
and finally, call the function when passing the data prop to the Line component
<Line
data={chartExample1[bigChartData + bigChartTime](chrtState.repos)}
Although looking at those functions they seem to have the same code, is just the data is changing, you could use a single function.
UPDATE
this would be the complete version of the component
const [chrtState, setChrtState] = useState({
loading: true,
repos: null,
});
useEffect(() => {
setChrtState({ loading: true });
const apiUrl = `http://example.com/api/request/`;
axios
.get(apiUrl, {
withCredentials: true,
})
.then((res) => {
const values = res.data.characters.reduce(
(acc, character) => ({
labels: [...acc.labels, character.label],
data: [...acc.data, character.data],
}),
{ labels: [], data: [] }
);
setChrtState({ loading: false, repos: values });
});
}, [setChrtState]);
if (chrtState.loading) {
return <span>Loading</span>;
}
return (
<Line
data={chartExample1[bigChartData + bigChartTime](chrtState.repos)}
options={chartExample1.options}
/>
);
I am using Highcharts React wrapper in an app using Hooks, when my chart is either loaded or zoomed it fires both setExtremes and setAfterExtremes multiple times each. I've looked through for similar questions but they are related to different issues.
I've reduced the code to the minimum setup, the page is not refreshing, the data is only parsed once and added to the chart once yet, animation is disabled and it's still consistently firing both events 7 times on:
* initial population
* on zoom
Versions: react 16.9, highcharts 7.2, highcharts-react-official 2.2.2
Chart
<HighchartsReact
ref={chart1}
allowChartUpdate
highcharts={Highcharts}
options={OPTIONS1}
/>
Chart Options:
const [series1, setSeries1] = useState([]);
const OPTIONS1 = {
chart: {
type: 'spline',
zoomType: 'x',
animation: false
},
title: {
text: ''
},
xAxis: {
events: {
setExtremes: () => {
console.log('EVENT setExtremes');
},
afterSetExtremes: () => {
console.log('EVENT sfterSetExtremes');
}
},
},
plotOptions: {
series: {
animation: false
}
},
series: series1
};
Data Population:
useEffect(() => {
if (data1) {
const a = [];
_.each(data1.labels, (sLabel) => {
a.push({
name: sLabel,
data: [],
})
});
... POPULATES DATA ARRAYS...
setSeries1(a);
}
}, [data1]);
Rather the question is old I also faced the same situation. The solution is to move the chart options to a state variable. Then the event will not fire multiple times.
It is mentioned on the library docs. https://github.com/highcharts/highcharts-react -- see the "optimal way to update"
import { render } from 'react-dom';
import HighchartsReact from 'highcharts-react-official';
import Highcharts from 'highcharts';
const LineChart = () => {
const [hoverData, setHoverData] = useState(null);
// options in the state
const [chartOptions, setChartOptions] = useState({
xAxis: {
categories: ['A', 'B', 'C'],
events: {
afterSetExtremes: afterSetExtremes,
},
},
series: [
{ data: [1, 2, 3] }
],
});
function afterSetExtremes(e: Highcharts.AxisSetExtremesEventObject) {
handleDateRangeChange(e);
}
return (
<div>
<HighchartsReact
highcharts={Highcharts}
options={chartOptions}
/>
</div>
)
}
render(<LineChart />, document.getElementById('root'));```
Your useEffect is getting fired multiple times probably because you are checking for data1 and data1 is changing. have you tried putting an empty array in your useEffect and see if it is firing multiple times?
if it only fires up once then the problem is that your useEffect is checking for a value that is constantly changing
if it still fires multiple times then there is something that is triggering your useEffect
I struggled the same problem after I SetState in useEffect().
My problem was I did a (lodash) deepcopy of the Whole options.
This also create a new Event every time.
// Create options with afterSetExtremes() event
const optionsStart: Highcharts.Options = {
...
xAxis: {
events: {
afterSetExtremes: afterSetExtremesFunc,
}
},
....
// Save in state
const [chartOptions, setChartOptions] = useState(optionsStart);
// On Prop Change I update Series
// This update deepcopy whole Options. This adds one Event Every time
React.useEffect(() => {
var optionsDeepCopy = _.cloneDeep(chartOptions);
optionsDeepCopy.series?.push({
// ... Add series data
});
setChartOptions(optionsDeepCopy);
}, [xxx]);
The fix is to Only update the Series. Not whole Options.
React.useEffect(() => {
var optionsDeepCopy = _.cloneDeep(chartOptions);
optionsDeepCopy.series?.push({
// ... Add series data
});
const optionsSeries: Highcharts.Options = { series: []};
optionsSeries.series = optionsDeepCopy.series;
setChartOptions(optionsSeries);
}, [xxx]);
My query was taking more than 2 mins to execute, henceforth it was getting timeout in browser. So now I have break the query and now running as a separate APIs which is helpful, but now I don't know how to handle these three requests so that it can render the data.
Note: The API's data are getting stored in the State component of react, here it is "Data".
Now I have a logic but can anyone give me a direction how to implement it.
Logic: Before storing the result of API's directly into state component, we can store it into different array, then we can iterate through this array for the use of pie chart then this data can be stored into the state component which can be used to render the pie chart in "Render" function.
Here the I am making three different API calls at the same time and storing it, here the result of the API's are directly been stored in the state component:
componentDidMount() {
Promise.all([
fetch("http://localhost:4000/api/EMEA/E_claimQuarter"),
fetch("http://localhost:4000/api/EMEA/E_claimQuarter1"),
fetch("http://localhost:4000/api/EMEA/E_claimQuarter2")
])
.then(([res1, res2, res3]) =>
Promise.all([res1.json(), res2.json(), res3.json()]))
.then(([data1, data2, data3]) =>
this.setState({
// Data: data1, data2, data3,
Data: {
labels: [
"FY19 Q1[NOV-JAN]",
"FY19 Q2[FEB-APR]",
"FY18 Q3[SEP-NOV]"
],
datasets: [
{
label: "",
data: data1,
backgroundColor: [
"rgba(255,105,145,0.6)",
"rgba(155,100,210,0.6)",
"rgb(63, 191, 191)"
]
}
]
}
})
);
}
This is how you handle the data form API and loop through it then render this data for the various charts which is in my case is Pie Chart:
ComponentDidMount() {
axios.get(`http://localhost:4000/api/APJ/A_claimQuarter`)
***************************************************************
.then(res => {
const claims = res.data;
let claim = [];
claims.forEach(element => {
claim.push(element.CNT1);
});
********************************************************************
this.setState({
Data: {
labels: ['FY19 Q1[NOV-JAN]','FY19 Q2[FEB-APR]','FY18[SEP-NOV]'],
datasets:[
{
label:'',
data: claim ,
backgroundColor:[
'rgba(255,105,145,0.6)',
'rgba(155,100,210,0.6)',
'rgb(63, 191, 191)'
]
}
]
}
});
})
}
I have made some modifications and now it is working fine for me, if anyone want the answer you can look at mine, it is 100% working:
constructor(props) {
super(props);
this.state = {
Data: []
};
}
componentDidMount() {
Promise.all([
fetch("http://localhost:4000/api/EMEA/E_claimQuarter"),
fetch("http://localhost:4000/api/EMEA/E_claimQuarter1"),
fetch("http://localhost:4000/api/EMEA/E_claimQuarter2")
])
.then(([res1, res2, res3]) => Promise.all([res1.json(), res2.json(), res3.json()]))
.then(([data1, data2, data3]) =>
{
console.log(typeof(data1));
const array = [...data1, ...data2, ...data3];
// const A = JSON.strigify(array);
console.log('hi');
console.log(array);
console.log(data1);
// console.log(A);
let claim = [];
array.forEach(element => {
claim.push(element.COUNT);
});
console.log(claim);
this.setState({
// Data: data1, data2, data3,
Data: {
labels: [
"FY19 Q1[NOV-JAN]",
"FY19 Q2[FEB-APR]",
"FY18 Q3[SEP-NOV]"
],
datasets: [
{
label: "",
data: claim,
backgroundColor: [
"rgba(255,105,145,0.6)",
"rgba(155,100,210,0.6)",
"rgb(63, 191, 191)"
]
}
]
}
})
});
}
Based on OP's own answer, here's a more generalised solution :
componentDidMount(graphData) {
return Promise.all(graphData.map(dataObj => dataObj.url))
.then(results => Promise.all(results.map(res => res.json())))
.then(results => this.setState({
'Data': {
'labels': graphData.map(dataObj => dataObj.label),
'datasets': [
{
'label': '',
'data': results.reduce((prev, next) => prev.concat(next), []),
'backgroundColor': graphData.map(dataObj => dataObj.bgColor)
}
]
}
}));
}
As you see, Array methods .map() and .reduce() make for some nice compact code.
Call as follows:
var quartersData = [
{ 'url':'http://localhost:4000/api/EMEA/E_claimQuarter', 'label':'FY19 Q1[NOV-JAN]', 'bgColor':'rgba(255,105,145,0.6)' },
{ 'url':'http://localhost:4000/api/EMEA/E_claimQuarter1', 'label':'FY19 Q2[FEB-APR]', 'bgColor':'rgba(155,100,210,0.6)' },
{ 'url':'http://localhost:4000/api/EMEA/E_claimQuarter2', 'label':'FY18 Q3[SEP-NOV]', 'bgColor':'rgb(63, 191, 191)' }
];
componentDidMount(quartersData)
.then(() => {
console.log('complete');
});