Without setTimeout component is not rendering the proper data - reactjs

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.

Related

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-chartjs-2 does not show line until window is resized

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

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.

state is not being updated when using React Hooks

I am currently playing around with the new React Hooks feature, and I have run into an issue where the state of a functional component is not being updated even though I believe I am doing everything correctly, another pair of eyes on this test app would be much appreciated.
import React, { useState, useEffect } from 'react';
const TodoList = () => {
let updatedList = useTodoListState({
title: "task3", completed: false
});
const renderList = () => {
return (
<div>
{
updatedList.map((item) => {
<React.Fragment key={item.title}>
<p>{item.title}</p>
</React.Fragment>
})
}
</div>
)
}
return renderList();
}
function useTodoListState(state) {
const [list, updateList] = useState([
{ title: "task1", completed: false },
{ title: "task2", completed: false }
]);
useEffect(() => {
updateList([...list, state]);
})
return list;
}
export default TodoList;
updateList in useTodoListState's useEffect function should be updating the list variable to hold three pieces of data, however, that is not the case.
You have a few problems here:
In renderList you are misusing React.Fragment. It should be used to wrap multiple DOM nodes so that the component only returns a single node. You are wrapping individual paragraph elements each in their own Fragment.
Something needs to be returned on each iteration of a map. This means you need to use the return keyword. (See this question for more about arrow function syntax.)
Your code will update infinitely because useEffect is updating its own hook's state. To remedy this, you need to include an array as a second argument in useEffect that will tell React to only use that effect if the given array changes.
Here's what it should all look like (with some reformatting):
import React, { useState, useEffect } from 'react';
const TodoList = () => {
let updatedList = useTodoListState({
title: "task3", completed: false
});
return (
<React.Fragment> // #1: use React.Fragment to wrap multiple nodes
{
updatedList.map((item) => {
return <p key={item.title}>{item.title}</p> // #2: return keyword inside map with braces
})
}
</React.Fragment>
)
}
function useTodoListState(state) {
const [list, updateList] = useState([
{ title: "task1", completed: false },
{ title: "task2", completed: false }
]);
useEffect(() => {
updateList([...list, state]);
}, [...list]) // #3: include a second argument to limit the effect
return list;
}
export default TodoList;
Edit:
Although I have tested that the above code works, it would ultimately be best to rework the structure to remove updateList from useEffect or implement another variable to control updating.

Resources