react-chartjs-2 does not show line until window is resized - reactjs

I'm having this weird bug with my React app with the react-chartjs-2. Chart data does not show correctly until I resize window. Tested on Firefox and Chrome, same result. Data is there, but I don't know if the data is loaded after the render or something. Please, I am student and not an expert, so please explain in detail if you know the solution for my problem. Thank you!
Youtube video of my bug
import React, {Component} from 'react';
import {Line} from 'react-chartjs-2';
const options = {
title: {
display: true,
text: "Chart Title"
},
scales: {
yAxes: [
{
ticks: {
suggestedMin: 20,
suggestedMax: 50
}
}
]
}
};
const legend = {
display: true,
position: "bottom",
labels: {
fontColor: "#323130",
fontSize: 14
}
};
class SensorChart extends Component {
constructor(props) {
super(props);
this.chartReference = React.createRef()
this.state = {
data : {
labels: props.labels,
datasets: [
{
label: "Humidity",
borderColor: "rgb(9,0,192)",
data: props.humidity
},
{
label: "Temperature",
borderColor: "red",
data: props.temperature
}
]
}
}
}
componentDidMount() {
console.log(this.state.data.datasets[0].data)
console.log(this.state.data.datasets[1].data)
}
render() {
return (
<div>
<h5>Line Example</h5>
<Line ref={this.chartReference} data={this.state.data} legend={legend} options={options}/>
</div>
);
}
}
export default SensorChart;
import React from 'react';
import Icon from '#mdi/react'
import {mdiThermometer, mdiWaterPercent, mdiCalendarRange} from '#mdi/js';
import './SensorData.css';
import SensorChart from "./SensorChart";
function SensorData() {
const [humi, setHumi] = React.useState("");
const [temp, setTemp] = React.useState("");
const [date, setDate] = React.useState("");
const [labels] = React.useState([]);
const [humidity] = React.useState([]);
const [temperature] = React.useState([]);
function fetchData() {
fetch('https://myAPIurl/datalist/100')
.then(response => response.json())
.then(responseData => {
setDate(responseData[0].stringTime);
setHumi(responseData[0].humidity);
setTemp(responseData[0].temperature);
for (let i = 0; i < 100; i++) {
labels.push(responseData[i].stringTime)
humidity.push(responseData[i].humidity)
temperature.push(responseData[i].temperature)
}
})
}
React.useEffect(() => {
fetchData();
}, []);
return (
<div className="App">
<h1>Sensor data</h1>
<h3><Icon path={mdiWaterPercent}/> Humidity: {humi}%</h3>
<h3><Icon path={mdiThermometer}/>Temperature: {temp}°C</h3>
<h3><Icon path={mdiCalendarRange}/>Time: {date}</h3>
<div className="Chart">
<SensorChart labels={labels} humidity={humidity} temperature={temperature}/>
</div>
</div>
);
}
export default SensorData;
Console logs after site is loaded
console.log(this.state.data.datasets[0].data)
console.log(this.state.data.datasets[1].data)
_chartjs: {…}, push: ƒ, pop: ƒ, shift: ƒ, splice: ƒ, …]0: 371: 372: 353: 354: 355: 366: 387: 388: 409: 4110: 4711: 4312: 4013: 3714: 3615: 3516: 3717: 3618: 3619: 3620: 3621: 3622: 3623: 3624: 3625: 3626: 3627: 3628: 3629: 3630: 3631: 3632: 3633: 3634: 3635: 3736: 3737: 3738: 3739: 3840: 3841: 3842: 3843: 3844: 3845: 3846: 3947: 3948: 3949: 4050: 4151: 4152: 4153: 4154: 4255: 4356: 4357: 4458: 4459: 4360: 3961: 4062: 3963: 3864: 3765: 3666: 3667: 3768: 3769: 3770: 3771: 3672: 3673: 3574: 3575: 3676: 3577: 3678: 3679: 3580: 3681: 3782: 3783: 3784: 3885: 3886: 3887: 3888: 3789: 3890: 3891: 3792: 3793: 3794: 3795: 3796: 3797: 3798: 3799: 37length: 100_chartjs: {listeners: Array(1)}push: ƒ ()pop: ƒ ()shift: ƒ ()splice: ƒ ()unshift: ƒ ()__proto__: Array(0)
SensorChart.js:61 [_chartjs: {…}, push: ƒ, pop: ƒ, shift: ƒ, splice: ƒ, …]0: 24.31: 24.32: 24.33: 24.24: 24.15: 23.86: 22.87: 22.98: 23.59: 2410: 24.811: 24.812: 24.713: 24.514: 24.515: 24.516: 24.617: 24.518: 24.519: 24.520: 24.421: 24.422: 24.423: 24.424: 24.525: 24.426: 24.427: 24.428: 24.429: 24.430: 24.431: 24.432: 24.433: 24.434: 24.335: 24.436: 24.437: 24.538: 24.539: 24.540: 24.541: 24.442: 24.543: 24.544: 24.545: 24.546: 24.547: 24.448: 24.349: 24.450: 24.451: 24.452: 24.453: 24.554: 24.555: 24.556: 24.557: 24.558: 24.559: 24.560: 24.561: 24.562: 24.563: 24.564: 24.565: 24.566: 24.567: 24.468: 24.469: 24.470: 24.471: 24.472: 24.373: 24.374: 24.375: 24.376: 24.377: 24.378: 24.379: 24.380: 24.381: 24.482: 24.483: 24.384: 24.385: 24.386: 24.387: 24.388: 24.389: 24.490: 24.291: 24.392: 24.293: 24.294: 24.295: 24.296: 24.197: 24.198: 24.299: 24.1length: 100_chartjs: {listeners: Array(1)}push: ƒ ()pop: ƒ ()shift: ƒ ()splice: ƒ ()unshift: ƒ ()__proto__: Array(0)

For everyone who is still ending up here, this seems to be an actively tracked issue at the moment since about 2017 on their GitHub Repo (#90). Although the issue is closed at the moment (2021-Apr-01), it is obviously still a problem for a lot of users as it has received comments on GitHub itself quite recently as well as here on Stack Overflow.
Previously listed solutions:
Redraw:
Apparently including the redraw prop on the React-ChartJS-2 component call is supposed to force a redraw of the data:
return (
<Line data={chartData} options={options} title="My Chart" ref={chartRef} redraw />
)
Sadly, this did nothing for me...
useEffect():
By tying the charts actual data into a useState() hook, it will trigger a component reload every time you change the data, which if you plan it right can also include initial data load.
const [chartData, setChartData] = useState(null);
// Data Fetch
useEffect(() => {
//...Async fetch my data from source and update state
});
// Attempt at triggering update whenever datasets change:
useEffect(() => {
if (chartData !== null) {
chartRef.current.chartInstance.update({
preservation: true,
});
}
}, [chartData]);
// Nested component to trigger rerender of only the graph for performance
const Graph = () => {
return (
<>
{chartData ? (
<Line data={chartData} options={options} title="My Chart" ref={chartRef} redraw />
) : null}
</>
);
};
return (
<Graph />
)
Sadly (again), this also did nothing...
Conclusion:
This appears to be a bug with how ChartJS was ported to React, meaning this appears to be an issue with the actual React-ChartJS-2 package.
TL;DR
Its a known issue on the React-ChartJS-2 GitHub (#90). Normal approaches do not work currently. Stay tuned there for updates.

After a few tinkering about I finally managed to get the Line graph to re-render when the data is received.
You may need to make use of both useState and useEffect, how I got around it is by updating the state to hold the datasets when data received has changed
const [data, setData] = React.useState<any>({ datasets: [{ data: [] }] });
And the useEffect with the data sets and labels as the dependencies
React.useEffect(() => {
setData({
labels: label,
datasets: [
{
data: datavals,
borderColor: "#59F5FF",
backgroundColor: "none",
pointBackgroundColor: "#59F5FF",
pointBorderWidth: 5,
pointHoverRadius: 10,
tension: 0.4
}
]
});
}, [label, datavals]);
then further down in the Line component
return <Line data={data} />;

Related

Without setTimeout component is not rendering the proper data

This question is merely for curiosity. So, I have a parent component that fetches some data (Firebase) and saves that data in the state, and also passes the data to the child. The child's code is the following:
import { Bar, Doughnut } from 'react-chartjs-2'
import { Chart } from 'chart.js/auto'
import { useState, useEffect } from 'react'
import _ from 'lodash'
const initialData = {
labels: [],
datasets: [
{
id: 0,
data: [],
backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56', '#FF6384'],
color: ['#FF6384', '#36A2EB', '#FFCE56', '#FF6384'],
borderWidth: 0,
},
],
}
var config2 = {
maintainAspectRatio: true,
responsive: true,
}
export default function DoughnutChart(props) {
const [data, setData] = useState(initialData)
useEffect(() => {
const newData = _.cloneDeep(initialData)
var wins = 0
var losses = 0
var labels = ['wins', ' losses']
//Whithout seTimeout the chart is not updated
setTimeout(() => {
props.trades.forEach((trade) => {
if (trade.cprice) {
if (trade.cprice >= trade.price) {
wins++
} else {
losses++
}
}
})
newData.datasets[0].data = [wins, losses]
newData.labels = labels
setData(newData)
}, 1000)
}, [props.trades])
return (
<Doughnut
data={data}
options={config2}
redraw
/>
)
}
As you can see the child listens with useEffect to props changes, and then I process the props as I want to plot the necessary information on the Chart.
The thing is that in the beginning, the code didn't work (the Chart didn't display anything despite the props changed), I console logged the props, and it seems that something was happening too fast (if I console the length of the props.trades it showed me 0, but if I consoled the object it shows data in it) So that the forEach statement wasn't starting to iterate in the first place. When I added the setTimeout it started working if a put a 1000 milliseconds (with 500 milliseconds it doesn't work).
I'm a beginner at React and would be very interested in why this happens and what is the best approach to handle these small delays in memory that I quite don't understand.

Fetch data for ag-grid in Remix

I'm learning Remix.run and trying to figure out how to face some requirements
in advance. According to the documentation there is something called Resource Routes. But seems that a Resource Route need to be linked from a Link component:
<Link to="pdf" reloadDocument>
View as PDF
</Link>
I can't found any example showing how to create a simple route that can return data for a grid component, for example ag-grid.
There is any way to do this inside Remix or I will need to implement an external endpoint?
AG Grid wrote a blog post about this not too long ago. Here is the article: https://blog.ag-grid.com/using-ag-grid-react-ui-with-remix-run/.
First, set up a resource route using Remix's conventions outlined here: https://remix.run/docs/en/v1/guides/resource-routes#creating-resource-routes
The resource route should export only a loader function that retrieves the data you want to load into the table.
Note: This example also uses logic for infinite scrolling
app/routes/posts/$id/postsGridData.ts
import type { LoaderFunction } from 'remix';
import { db } from '~/utils/db.server'; // Prisma ORM being used
export const loader: LoaderFunction = ({ request }) => {
const from = Number(new URL(request.url).searchParams.get("from"));
const to = Number(new URL(request.url).searchParams.get("to"));
if (from >= 0 && to > 0) {
const posts = await db.post.findMany({
skip: from,
take: to - from,
select: {
id: true,
title: true,
updatedAt: true,
author: {
select: {
email: true,
name: true,
},
},
},
});
return posts;
}
return [];
}
Next, in the route with your AGGridReact component, you'll add the following:
A Remix Fetcher to get the data from your resource route without a route change
An onGridReady function that loads the next batch of data
Some local state to manage the fetching logic
A datasource to plug into AG Grid
A useEffect function to trigger when the fetcher has loaded
AgGridReact component with added parameters rowModelType and onGridReady
app/routes/posts.tsx
import { useFetcher } from 'remix';
import { useCallback, useEffect, useState } from 'react';
import { AgGridReact } from "ag-grid-react";
import AgGridStyles from "ag-grid-community/dist/styles/ag-grid.css";
import AgThemeAlpineStyles from "ag-grid-community/dist/styles/ag-theme-alpine.css";
export default function PostsRoute() {
const [isFetching, setIsFetching] = useState(false);
const [getRowParams, setGetRowParams] = useState(null);
const posts = useFetcher();
const onGridReady = useCallback((params) => {
const datasource = {
getRows(params) {
if (!isFetching) {
posts.load(`/posts?from=${params.startRow}&to=${params.endRow}`);
setGetRowParams(params);
setIsFetching(true);
}
},
};
params.api.setDatasource(datasource);
}, []);
useEffect(() => {
// The useEffect hook in this code will trigger when the fetcher has
// loaded new data. If a successCallback is available, it’ll call it,
// passing the loaded data and the last row to load
if (getRowParams) {
const data = posts.data || [];
getRowParams.successCallback(
data,
data.length < getRowParams.endRow - getRowParams.startRow
? getRowParams.startRow
: -1
);
}
setIsFetching(false);
setGetRowParams(null);
}, [posts.data, getRowParams]);
const columnDefs = [/* Your columnDefs */];
return (
<div className="ag-theme-alpine" style={{ width: "100%", height: "100%" }}>
<AgGridReact
columnDefs={columnDefs}
rowModelType="infinite"
onGridReady={onGridReady}
/>
</div>
);
}

React Hooks : mapping data from api call to render pie chart

need some help here. Thank you!
I am making a call to an api which returns an object with two numbers and a list, like so ...
{2, 1 , []}
The codebase I am working with is using 'use-global-hook' for Redux state management.
So I have storeState, storeActions as global hooks.
So in my render I have verified that the number values exist.
return
(
<h3> storeState.value1</h3> //prints 2
<h3> storeState.value2 </h3> //prints 1
)
Tried mapping this way and this throws too many renders error.
How can I map the dataset to Pie chart ?
const test1 = (props) =>
{
const [piChartData, setpiChart] = useState({});
React.useEffect(()=>{
storeActions.callApi();
},[]);
function drawPieChart()
{
setpiChart({
labels: ['label1', 'label2'],
datasets:[{
data:[storeState.value1, storeState.value2],
backgroundColor: ['red','green']
}]
})
}
return(
{drawPieChart()}
<Pie data={piChartData}/>
)
}
drawPieChart calls setpiChart updating the state and causing the component to re-render, on each render, you call drawPieChart, which calls setpiChart , causing an infinite loop of rendering,
remove drawPieChart() from render and put it in useEffect so it gets called when the component mounts, if callApi is aync and returns a promise, use .then :
const test1 = (props) => {
const [piChartData, setpiChart] = useState({});
React.useEffect(() => {
storeActions.callApi();
drawPieChart();
/**
* OR
* storeActions.callApi().then(() => drawPieChart(););
*/
}, []);
function drawPieChart() {
setpiChart({
labels: ["label1", "label2"],
datasets: [
{
data: [storeState.value1, storeState.value2],
backgroundColor: ["red", "green"]
}
]
});
}
return <Pie data={piChartData} />;
};

React-Hooks: Fetching data results into empty 'hits' array

I'm trying to fetch data with React Hooks. It all seems to work but the hits array is empty even though the data is fetched correctly.
Here's my code:
import React, { useState, useEffect } from 'react';
import MUIDataTable from "mui-datatables";
import { createMuiTheme, MuiThemeProvider, withStyles } from '#material-ui/core/styles';
export default function Dashboard(props) {
var classes = useStyles();
var theme = useTheme();
// local
var [mainChartState, setMainChartState] = useState("monthly");
const [data, setData] = useState({ hits: [] });
const url = my_url;
useEffect(() => {
const fetchData = async () => {
const result = await axios(
url,
);
setData(result.data);
console.log(result.data);
console.log(data);
};
fetchData();
}, []);
return (
<Grid item xs={12}>
<MuiThemeProvider theme={getMuiTheme()}>
<MUIDataTable
title="Analyzed DAOs"
data={data.hits}
columns={["Name", "Members", "Proposals", "Voters"]}
options={{
filterType: "checkbox",
}}
/>
</MuiThemeProvider>
</Grid>
)
}
When printing out the result.data, I get an array with 5 objects (as it should be) but when printing out the data.hits the result is am empty array, and the table shows zero rows.
What am I missing? Probably a lifecycle issue, but how do I fix it?
I'm the OP. Looks like for the code, as written in my question, to work, my data needs to be wrapped with a bit of json.
My data, as it comes from the server, is a json array. To make it work I did the following:
var jsonData = {};
jsonData.hits = result.data;
setData(jsonData);
That's it. Now it all works. It's a workaround and there's probably a more elegant solution.
Setting setData({ hits: [result.data] }) will allow your result to be assigned to data.hits.
setData will completely override any default or current value for data.
Actually your code works as expected, I guess you just need to keep the MUIDataTable columns matches the properties and the data structure that returned from your API.
Assume your returned data look like this:
const data = [
{
name: "Joe James",
company: "Test Corp",
city: "Yonkers",
state: "NY"
...
},
// more objects
];
You will need to set your columns like this:
const columns = [
{
name: "name", // This should match the data property
label: "Name", // Label will be shown
options: {...}
},
{
name: "company",
label: "Company",
options: {...}
},
{
// More columns for `city` and `state` etc...
}
Please follow this codeSandbox example.

How To Handle conditional Routing or Component Navigation Without React Router

I need to navigate between components based on several conditions, and I do not want routes to be displayed in the browser, say localhost:3000/step1 or localhost:3000/step2. The whole application is guided so that a user have to answer all the steps to reach the final result.
ATM I have a main container which handles the component rendering based on the Redux store value.
import React, { Component } from "react";
class Home extends Component {
renderComponent = screen => {
switch (screen) {
case 'SCREEN_A':
return <ScreenA />;
case 'SCREEN_B':
return <ScreenB />;
case 'SCREEN_C':
return <ScreenC />;
case 'SCREEN_D':
return <ScreenD />;
default:
return <ScreenA />;
}
};
render() {
return <div>{this.renderComponent(this.props.currentScreen)}</div>;
}
}
function mapStateToProps(storeData) {
return {
store: storeData,
currentScreen: storeData.appState.currentScreen,
userData: storeData.userData
};
}
export default connect(mapStateToProps)(Home);
The problem is I have to use dispatch to trigger navigation
navigateTo(screens[destination])
navigateBackward(currentScreen)
navigateForward(currentScreen)
in almost all components. I do have a predefined JSON for each component which contains the destination for each screen.
screens : {
SCREEN_A:{
id: 1,
name: 'SCREEN_A',
next: 'SCREEN_B',
back: 'WELCOME_SCREEN',
activeLoader: true,
},
SCREEN_B:{
id: 2,
name: 'SCREEN_B',
next: 'SCREEN_C',
back: 'WELCOME_SCREEN',
activeLoader: true,
},
SCREEN_C:{
id: 3,
name: 'SCREEN_C',
next: 'SCREEN_D',
back: 'SCREEN_A',
activeLoader: true,
},
SCREEN_D:{
id: 4,
name: 'SCREEN_D',
next: 'SCREEN_E',
back: 'SCREEN_D',
activeLoader: true,
},
}
And there are protected screens which makes things way more complicated. Is there a better way of doing this with redux? or should I create a middleware and intercept each state change and calculate the next screen.
I would change a few things:
Make your steps/screens dynamic. By putting them into an Array and using the index to determine the current step it removes a lot of code and will make it easier to add/move steps.
Store the steps/screens config in the redux store.
Optionally, you can pass the nextStep and previousStep to the StepComponent. e.g. <StepComponent nextStep={nextStep} previousStep={previousStep} />.
In your last step, you probably want to call a different action instead of nextStep.
Here's what my solution would look like:
// Home.jsx
import React, { Component } from 'react';
import * as types from '../../redux/Actor/Actor.types';
class Home extends Component {
stepComponents = [
ScreenA,
ScreenB,
ScreenC,
ScreenD,
];
render() {
const { step, steps } = this.props;
const StepComponent = this.stepComponents[step];
return (
<div>
<StepComponent {...steps[step]} />
</div>
);
}
}
// store.jsx
export default {
step : 0,
steps: [
{
id : 1,
name : 'SCREEN_A',
activeLoader: true,
},
....
],
};
// actions.jsx
export const nextStep = () => ({ type: 'NEXT_STEP' });
export const previousStep = () => ({ type: 'PREVIOUS_STEP' });
// reducers.jsx
export const nextStep = state => ({ ...state, step: state.step + 1 });
export const previousStep = state => ({ ...state, step: state.step - 1 });

Resources