Chart created using react-chartjs-2 and chartjs is refreshing 2 times on changing values. For the first time, only the shape of the graph is changing and for the second time, the values on the y-axis and x-axis are changing.
const Chart = ({country}) => {
const [dailyData,setDailyData] = useState([]);
useEffect(() => {
const fetchAPI = async () => {
setDailyData(await fetchDailyData(country))
}
fetchAPI();
},[country]);
const {day,infected,recoveries,deaths} = dailyData;
const lineChart = (
infected? (<Line
data={{
labels: day.map((data ) => (data)),
datasets:[
{
data: infected.map((data) => (data)),
label: 'Infected',
borderColor: '#3333ff',
fill: true,
}],
}}
/> ) : null
)
There is an issue with your dailyData state.
You set the default value as an empty array but then later you try to destructure it as an object.
const asd=[1,2,4,5];
const {a,b,c}=asd;
console.log (a,b,c); // undefined, undefined, undefined
const [e,f,g]=asd;
console.log (e,f,g); // 1,2,4
However, you said your chart does work after the second flash. Meaning your original empty array is not the right default value for the useState hook. Should use null and check this before you destructure it.
UPDATE:
The chart could flash twice if the data is loaded twice too. useEffect should have an if statement checking if the data was loaded already and load only if it was not. Possibly you could add another state for loading. so you would not have two concurrent API calls.
Your code would look like this:
const Chart = ({country}) => {
const [dailyData,setDailyData] = useState(null);
useEffect(() => {
if(country && !dailyData){
const fetchAPI = async () => {
setDailyData(await fetchDailyData(country))
}
fetchAPI();
}
},[country]);
let lineChart=null
if (dailyData){
const {day,infected,recoveries,deaths} = dailyData;
lineChart = (<Line
data={{
labels: day.map((data ) => (data)),
datasets:[
{
data: infected.map((data) => (data)),
label: 'Infected',
borderColor: '#3333ff',
fill: true,
}],
}}
/> )
}
}
Related
I am quite new to Arcgis-JS and React. As suggested here, I am using a functional component with the useEffect hook to integrate my map.
Now I want to display a line within my map when I click on a certain row of a list. On click I am fetching the appropriate coordinates to be displayed and storing them to a context-variable (dbPageContext.currentGeom).
The problem: When I want to display another line, the entire map-component has to re-render as I am passing the line-array-variable as a second argument to the useEffect hook.
const MapComp = () => {
const mapRef = useRef();
const dbPageContext = useContext(DbPageContext);
useEffect(() => {
const mainMap = new Map({
layers: [layer],
basemap: "arcgis-topographic", // Basemap layer service
});
const graphicsLayer = new GraphicsLayer();
mainMap.add(graphicsLayer);
const polyline = {
type: "polyline",
paths: dbPageContext.currentGeom,
};
const simpleLineSymbol = {
type: "simple-line",
color: [0, 230, 250],
width: 4,
};
const polylineGraphic = new Graphic({
geometry: polyline,
symbol: simpleLineSymbol,
});
graphicsLayer.add(polylineGraphic);
const view = new MapView({
container: mapRef.current,
map: mainMap,
spatialReference: {
wkid: 3857,
},
});
return () => {
view && view.destroy();
};
}, [dbPageContext.currentGeom]);
return (
<div>
<div className="webmap" ref={mapRef} />
</div>
);
};
export default MapComp;
How can I update only the graphics-layer without updating the entire map-component? Would be great if someone could help me finding a solution for that.
EDIT: I also tried to implement the map without using the useeffect hook. But then, nothing was displayed.
You need to separate the effects. On page load, you should have one effect that creates the map. Then a second effect can update the map when dbPageContext.currentGeom changes.
const MapComp = () => {
const mapRef = useRef();
const dbPageContext = useContext(DbPageContext);
// Memoize this, as you only need to create it once, but you also need
// it to be available within scope of both of the following useEffects
const graphicsLayer = React.useMemo(
() => new GraphicsLayer(),
[]
);
// On mount, create the map, view, and teardown
useEffect(() => {
const mainMap = new Map({
layers: [layer],
basemap: "arcgis-topographic", // Basemap layer service
});
const view = new MapView({
container: mapRef.current,
map: mainMap,
spatialReference: {
wkid: 3857,
},
});
mainMap.add(graphicsLayer);
return () => {
view && view.destroy();
};
}, [])
// When dbPageContext.currentGeom changes, add a polyline
// to the graphics layer
useEffect(() => {
const polyline = {
type: "polyline",
paths: dbPageContext.currentGeom,
};
const simpleLineSymbol = {
type: "simple-line",
color: [0, 230, 250],
width: 4,
};
const polylineGraphic = new Graphic({
geometry: polyline,
symbol: simpleLineSymbol,
});
// Clear previously added lines (if that's what you want)
graphicsLayer.removeAll()
graphicsLayer.add(polylineGraphic);
}, [dbPageContext.currentGeom]);
return (
<div>
<div className="webmap" ref={mapRef} />
</div>
);
};
export default MapComp;
I do the weather app and need some help. In component Chart in options and series comes [object Object]. When you change something in the code, it is displayed. I think that the problem with useEffect? but I don't know how to fix that
import React, { useContext, useState, useEffect } from 'react';
import Chart from 'react-apexcharts';
import { Context } from '../../contex';
const WeatherGrapth = () => {
const {dailyForecast} = useContext(Context);
const [category, setCategory] = useState([])
const [data, setData] = useState([])
useEffect(() => {
const day = [];
const temp =[];
const items = dailyForecast.map((d) => {
const unixTimestamp = d.dt;
const getTemp = Math.round(d.temp.day)
let getDay = new Date(unixTimestamp* 3600 * 24 * 1000).getDate();
day.push(getDay)
temp.push(getTemp)
})
setCategory(day)
setData(temp)
}, []);
return(
<div>
<Chart options={{
chart: {
id: 'weather-graph'
},
xaxis: {
categories: category,
title: {
text: 'Date',
},
},
yaxis: {
title: {
text: 'Temperature °C',
},
},
}}
series={[{
name: 'temp',
data: data
}]} type="line" height={'349px'} />
</div>
)
}
export default WeatherGrapth;
But as soon as I change something in the code, everything will update and a graph will appear.
As React doc says:
By default, effect runs both after the first render and after every update
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run
If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders.
Probably At first dailyForecast context is empty or has not any valid data and after that it fills with data you should pass it to useEffect as dependency to run the effect at changes:
const {dailyForecast} = useContext(Context);
const [category, setCategory] = useState([])
const [data, setData] = useState([])
useEffect(() => {
const day = [];
const temp =[];
const items = dailyForecast.map((d) => {
const unixTimestamp = d.dt;
const getTemp = Math.round(d.temp.day)
let getDay = new Date(unixTimestamp* 3600 * 24 * 1000).getDate();
day.push(getDay)
temp.push(getTemp)
})
setCategory(day)
setData(temp)
}, [dailyForecast]);
function MyMap({ floorsByBuilding }: MyMapProps) {
const [coords, setCoords] = useState<any[]>([]);
const created = (e) => {
const coordsList = e.layer.editing.latlngs[0][0];
console.log("coordsList:", coordsList);
setCoords(coordsList);
console.log("Coords:",coords);
};
return (
<div className="mapview">
<div className="myMap">
<Map center={[0, 0]} zoom={1}>
<FeatureGroup>
<EditControl
position="topright"
draw={{
rectangle: false,
circle: false,
circlemarker: false,
polyline: false,
}}
onCreated={created}
/>
</FeatureGroup>
</Map>
</div>
</div>
);
}
e.layer.editing.latlngs[0][0] looks like,
[
{
"lat":32.29840589562344,
"lng":80.85780182804785
},
{
"lat":17.421213563227735,
"lng":78.36653079164644
},
{
"lat":23.02755815843566,
"lng":107.33497479055386
},
{
"lat":41.49329414356384,
"lng":104.47883340910323
},
{
"lat":39.47390998063457,
"lng":82.8312041405605
}
]
The EditControl is react-leaflet-draw component that helps to annotate in an image or map, from where we get coordinates (the above data) which is e.layer.editing.latlngs[0][0].
After getting the coordinates, i'm trying to store those coordinates into a state (which is setCoords) so that i can use those coordinates elsewhere.
The issue here is,
after obtaining the coordsList for the first time, the setCoords doesn't actually set those coords ( the second console.log returns empty array, even if the first console.log does return desired output).
But when i'm trying the second time, i.e a new coordList values are obtain, the setCoords sets the data of the pervious coordsList (the first console.log return the coordsList correctly but the second console.log return previous data of coordsList).
Screenshots for more clarity,
First time when I get coordsList,
Second time,
Keep console.log outside the created() function. setCoords() is an async function, as a result state won't be updated right away to show it in the console.log as you have given. So keep it outside to view it when re-rendering.
const created = (e) => {
const coordsList = e.layer.editing.latlngs[0][0];
console.log("coordsList:", coordsList);
setCoords(coordsList);
};
console.log("Coords:",coords);
Because state only has new value when component re render. So you should move console.log to useEffect to check the new value when component re-render.
const created = (e) => {
const coordsList = e.layer.editing.latlngs[0][0];
console.log("coordsList:", coordsList);
setCoords(coordsList);
};
useEffect(() => {
console.log("Coords:", coords);
}, [coords]);
It's because Reactjs's update batching mechanism
When updating state within an eventHandler like onChange, onClick,.etc.., React will batch all of setState into one:
const created = (e) => {
const coordsList = e.layer.editing.latlngs[0][0];
console.log("coordsList:", coordsList);
setCoords(coordsList);
console.log("Coords:", coords);
};
Place your log right before return function to see that lastest value:
function MyMap({ floorsByBuilding }: MyMapProps) {
const [coords, setCoords] = useState<any[]>([]);
const created = (e) => {
const coordsList = e.layer.editing.latlngs[0][0];
setCoords(coordsList);
};
console.log("Coords:",coords);
return (
...
)
}
But this behaviour will be changed in Reactjs 18 as mentioned here
I am using function component in react with typescript. Here is what my component looks like
const Table = (props: TableProps) => {
const [gridApi, setGridApi] = React.useState(() => {})
const gridOptions = {
rowData: rowData,
columnDefs: columnDef,
pagination: true,
}
const onGridReady = (params) => {
setGridApi(params.api);
}
return (
<div className="ag-theme-alpine" >
<AgGridReact gridOptions={gridOptions} onGridReady={onGridReady}/>
</div>
);
};
I need to get the gridApi so I can use it in a handler for another component to do quick filtering.
Looking at the docs HERE, the recommended way is to grab the gridApi and store it in a state. Considering that I am using function component here is what I did
const [gridApi, setGridApi] = React.useState(() => {})
When I do this in the handler:
const handleTableFilter = (filterText: string) => {
gridApi.setQuickFilter(filterText) // error here - Property 'setQuickFilter' does not exist on type '() => void'.
}
Typescript complains that Property 'setQuickFilter' does not exist on type '() => void'.
I also tried the pattern recommended for Javascript HERE but I could not quite figure that out too.
I would appreciate some help on how to store the gridApi in my use case (Typescript, React using Function components - react hooks). If possible, I would prefer a solution where I will not have to store a function in useState. If not, I a fine with any solution.
Here is what I ended up doing to get GridApi without error in Typescript ( Mike Abeln comments in the question pointed me in the right direction)
const Table = (props: TableProps) => {
// - - - - omitted code for column & row data from props - - - -
const gridApiRef = useRef<any>(null); // <= defined useRef for gridApi
const [rowData, setRowData] = useState<any[]>([]);
const [columnDef, setColumnDef] = useState<any[]>([]);
// - - - - omitted code for useEffect updating rowData and ColumnDef - - - -
const gridOptions = {
pagination: true,
}
const onGridReady = (params) => {
params.api.resetRowHeights();
gridApiRef.current = params.api // <= assigned gridApi value on Grid ready
}
const handleTableFilter = (filterText: string) => {
gridApiRef.current.setQuickFilter(filterText); // <= Used the GridApi here Yay!!!!!
}
return (
<div className="ag-theme-alpine" >
<AgGridReact columnDefs={columnDef}
rowData={rowData}
gridOptions={gridOptions}
onGridReady={onGridReady}
ref={gridApiRef}/> // the gridApiRef here
</div>
);
};
Although I am seeing some performance issue on the table (the tables are huge with about 600K rows) but this answers the question on how to get GridApi and use it to filter
The type for the gridApiRef is <AgGridReact> from 'ag-grid-react/lib/agGridReact'.
Example:
import { AgGridReact } from 'ag-grid-react'
import { AgGridReact as AgGridReactType } from 'ag-grid-react/lib/agGridReact'
// Make sure it initialize as null.
const gridApiRef = useRef<AgGridReactType>(null);
const [rowData, setRowData] = useState<any[]>([]);
const [columnDef, setColumnDef] = useState<any[]>([]);
useEffect(() => {
fetch('url')
.then(res => res.json())
.then(data => setRowData(data)
}, [])
return (
<div className="ag-theme-alpine" >
<AgGridReact
ref={gridApiRef}
columnDefs={columnDef}
rowData={rowData}
gridOptions={gridOptions}/>
</div>
);
Here's a tutorial: https://thinkster.io/tutorials/using-ag-grid-with-react-getting-started. Not in typescript though.
new to react so I am not quite sure what I am doing wrong here... I am trying to call data from an API, then use this data to populate a charts.js based component. When I cmd + s, the API data is called in the console, but if I refresh I get 'Undefined'.
I know I am missing some key understanding about the useEffect hook here, but i just cant figure it out? All I want is to be able to access the array data in my component, so I can push the required values to an array... ive commented out my attempt at the for loop too..
Any advice would be greatly appreciated! My not so functional code below:
import React, {useState, useEffect} from 'react'
import {Pie} from 'react-chartjs-2'
const Piegraph = () => {
const [chartData, setChartData] = useState();
const [apiValue, setApiValue] = useState();
useEffect(async() => {
const response = await fetch('https://api.spacexdata.com/v4/launches/past');
const data = await response.json();
const item = data.results;
setApiValue(item);
chart();
},[]);
const chart = () => {
console.log(apiValue);
const success = [];
const failure = [];
// for(var i = 0; i < apiValue.length; i++){
// if(apiValue[i].success === true){
// success.push("success");
// } else if (apiValue[i].success === false){
// failure.push("failure");
// }
// }
var chartSuccess = success.length;
var chartFail = failure.length;
setChartData({
labels: ['Success', 'Fail'],
datasets: [
{
label: 'Space X Launch Statistics',
data: [chartSuccess, chartFail],
backgroundColor: ['rgba(75,192,192,0.6)'],
borderWidth: 4
}
]
})
}
return (
<div className="chart_item" >
<Pie data={chartData} />
</div>
);
}
export default Piegraph;
There are a couple issues that need sorting out here. First, you can't pass an async function directly to the useEffect hook. Instead, define the async function inside the hook's callback and call it immediately.
Next, chartData is entirely derived from the apiCall, so you can make that derived rather than being its own state variable.
import React, { useState, useEffect } from "react";
import { Pie } from "react-chartjs-2";
const Piegraph = () => {
const [apiValue, setApiValue] = useState([]);
useEffect(() => {
async function loadData() {
const response = await fetch(
"https://api.spacexdata.com/v4/launches/past"
);
const data = await response.json();
const item = data.results;
setApiValue(item);
}
loadData();
}, []);
const success = apiValue.filter((v) => v.success);
const failure = apiValue.filter((v) => !v.success);
const chartSuccess = success.length;
const chartFail = failure.length;
const chartData = {
labels: ["Success", "Fail"],
datasets: [
{
label: "Space X Launch Statistics",
data: [chartSuccess, chartFail],
backgroundColor: ["rgba(75,192,192,0.6)"],
borderWidth: 4,
},
],
};
return (
<div className="chart_item">
<Pie data={chartData} />
</div>
);
};
export default Piegraph;
pull your chart algorithm outside or send item in. Like this
useEffect(async() => {
...
// everything is good here
chart(item)
})
you might wonder why I need to send it in. Because inside useEffect, your apiValue isn't updated to the new value yet.
And if you put the console.log outside of chart().
console.log(apiData)
const chart = () => {
}
you'll get the value to be latest :) amazing ?
A quick explanation is that, the Piegraph is called whenever a state is updated. But this update happens a bit late in the next cycle. So the value won't be latest within useEffect.