How to update nested array and objects in react - reactjs

I'm trying to update the state in react. However, still not clear how shall I do it. The nested structure is something like :
this.state ={
permissionsObject:{
fruitsGroup:[
{
index:0,
taste:false,
color:false,
},
{
index:1,
taste:false,
color:false,
},
],
fruitsGroup:[
{
index:0,
taste:false,
color:false,
},
{
index:1,
taste:false,
color:false,
},
]
}
}
Now, if I want to update the value of fruitsGroup -> taste based on the index value then how shall I proceed. I found there are various solutions exists for nested structure. However, none of them were answering my query or they are using react hooks while I'm not. Thanks well in advance.
Edit: The fruitGroup will have different name. So, how can I even locate it dynamically while setting the state.

You can do it like this:
import React, { Component } from "react";
export class App extends Component {
state = {
permissionsObject: {
fruitsGroup: [
{
index: 0,
taste: false,
color: false,
},
{
index: 1,
taste: false,
color: false,
},
],
groupName: [
{
index: 0,
taste: false,
color: false,
},
{
index: 1,
taste: false,
color: false,
},
],
},
};
updateStateHandler = (idx) => {
const updatedFruitsGroup = this.state.permissionsObject.fruitsGroup.map(
(item) => (item.index === idx ? { ...item, taste: true } : item)
);
const newState = {
permissionsObject: {
...this.state.permissionsObject,
fruitsGroup: updatedFruitsGroup,
},
};
this.setState(newState);
};
render() {
return (
<div>
{JSON.stringify(this.state)}
<button onClick={() => this.updateStateHandler(0)}>
Update index 0
</button>
<button onClick={() => this.updateStateHandler(1)}>
Update index 1
</button>
</div>
);
}
}
export default App;

Related

update options without reseting the zoom

I am using React ApexChart in order to show off some data, and I have a switch that shows and hides the data labels. The options are changing without problems, but the zoom level is resetting every time. Is there a way to keep the zoom level when changing the options?
Here is a small preview of the component:
import Chart from "react-apexcharts";
import React, { useEffect, useState } from 'react';
import { Layout } from "antd";
interface Props {
series?: ApexAxisChartSeries;
chartOptiosn?: Record<string, any>;
[ key: string ]: any;
}
export const DynamicChart: React.FC<Props> = React.memo( ( props: Props ) => {
const { Content } = Layout;
const { series, chartOptions, uuid } = props;
const [ initialRerender, setInitialRerender ] = useState( false );
useEffect( () => {
if ( !initialRerender ) {
setInitialRerender( true );
}
}, [ initialRerender ] );
useEffect( () => {
}, [] );
useEffect( () => {
console.log( chartOptions );
}, [ chartOptions ] );
return (
<Layout>
<Content>
<Chart
options={ { ...structuredClone( chartOptions || {} ), chart: { ...chartOptions.chart, id: uuid } } }
series={ series }
type="area"
height={ 350 }
/>
</Content>
</Layout>
);
} );
Here are the options:
export const defaultOptions: ApexCharts.ApexOptions = {
series: [],
chart: {
animations: {
enabled: true,
easing: "linear",
dynamicAnimation: {
speed: 300
}
},
toolbar: {
show: true,
tools: {
download: `<svg viewBox="64 64 896 896" focusable: "false" name= "download" theme="outlined"><path d="M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"></path></svg>`,
selection: false,
zoom: true,
zoomin: true,
zoomout: true,
pan: true,
customIcons: []
},
},
zoom: {
enabled: true
}
},
dataLabels: {
enabled: false
},
yaxis: {
labels: {
style: {
fontSize: "14px",
fontWeight: 600,
colors: [ "#8c8c8c" ],
},
},
},
xaxis: {
type: "datetime"
}
};
Here is a demo for more depth of the problem that I am facing.
Just zoom in between the 3 seconds interval and after the labels reappear, the zoom is refreshed. Is there a way to stop that in react, or should I seek a vanilla approach and ditch the react solution all together?

Can't perform a React state update on an unmounted component, using class component and component did mount

I have certain code as below:-
class BarChart extends Component {
constructor(){
super();
this.state = {
chartData:{}
}
}
componentDidMount() {
this.getChartData();
}
getChartData() {
axios.get("http://localhost:5001/inventory/clusterscount").then(res => {
const myresponse = res.data;
console.log(myresponse)
let countcluster = [];
let listregion = [];
for (const dataobj of myresponse){
countcluster.push(parseInt(dataobj.clusterscount));
listregion.push(dataobj.region);
}
console.log(countcluster)
console.log(listregion)
this.setState({
chartData: {
labels:listregion,
datasets: [
{
label: "level of thiccness",
data: countcluster,
backgroundColor: ["rgba(75, 192, 192, 0.6)"],
borderWidth: 4
}
]
}
});
});
}
render(){
return (
<div className="App">
<h1>Dankmemes</h1>
<div>
<Line
data={this.state.chartData}
options={{
responsive: true,
title: { text: "THICCNESS SCALE", display: true },
scales: {
yAxes: [
{
ticks: {
autoSkip: true,
maxTicksLimit: 10,
beginAtZero: true
},
gridLines: {
display: false
}
}
],
xAxes: [
{
gridLines: {
display: false
}
}
]
}
}}
/>
</div>
</div>
);
}
}
export default BarChart;
Now while running it am getting the desired clusters and regions as below:-
0: {clusterscount: '2', region: 'test1'}
1: {clusterscount: '10', region: 'test2'}
2: {clusterscount: '8', region: 'test3'}
3: {clusterscount: '1', region: 'test4'}
4: {clusterscount: '8', region: 'test5'}
5: {clusterscount: '2', region: 'test6'}
I am able to get results for clustercount and listregion as well, but keep getting this error. I have tried multiple things but out of ideas.
But in logs am getting as below:-
Can someone help me with this?
The react useEffect expects a cleanup function to cancel subscription and asynchronus tasks so we need to check if component is mounted or not there are couple of ways we can do it and react community have good solution for that.
const LineChart = () =>{
const [chartData,setChartData]= useState({});
const [myresponse, setmyresponse] =useState([]);
const isMountedRef = useRef(null);
useEffect(() =>{
isMountedRef.current = true;
let countcluster = [];
let listregion = [];
axios.get("http://localhost:5001/inventory/clusterscount").
then(res=>{
if(isMountedRef.current){
const myresponse= res.data;
setmyresponse(myresponse)
console.log(myresponse);
for (const dataobj of myresponse){
countcluster.push(parseInt(dataobj.clusterscount));
listregion.push(dataobj.region);
}
setChartData({
labels: listregion,
datasets: [
{
label: "level of thiccness",
data: countcluster,
backgroundColor: ["rgba(75, 192, 192, 0.6)"],
borderWidth: 4
}
]
});
}
})
.catch(err=>{
console.log(err);
});
return () => isMountedRef.current = false;
},[])

(Lifecycle problem?) React Chart js-2 not updating data from componentDidMount

I am trying to populate chart data from my backend.
Although I am fetching data and pushing data in componentDidMount, the Bars or Scatters are not loaded on page load.
If I change my screen width in inspect mode in google dev tools, it starts loading which leads me to believe this is a lifecyle problem.
However, changing it to componentWillMount did not change anything. Putting a if statement before render like below just stops loading the chart altogether.
if(this.state.data.datasets[0].data.length ===0){
return null;
}
Any way to fix this problem?
import React, { Component } from "react";
import axios from "axios";
import { Bar, Scatter } from "react-chartjs-2";
export class Data extends Component {
constructor(props) {
super(props);
this.state = {
provider: [],
data: {
labels: ["Action", "Anime", "Children"],
datasets: [
{
label: "Total",
backgroundColor: "rgba(255, 159, 64, 0.4)",
borderColor: "white",
borderWidth: 1,
stack: 1,
hoverBackgroundColor: "rgba(255,99,132,0.4)",
hoverBorderColor: "rgba(255,99,132,1)",
data: []
},
{
label: "Above ⭐️8.5",
backgroundColor: "white",
type: "scatter",
showLine: false,
stack: 1,
data: []
}
]
}
};
}
componentDidMount() {
axios.get("http://localhost:8001/provider").then(res =>
this.setState({ provider: res.data }, () => {
this.pushAction();
})
);
}
pushAction() {
const dataState = this.state.data;
const oldDataTotal = this.state.data.datasets[0].data;
const oldDataGood = this.state.data.datasets[1].data;
oldDataTotal.push(this.state.provider[0].huluAction);
oldDataTotal.push(this.state.provider[0].huluAnime);
oldDataTotal.push(this.state.provider[0].huluChildren);
oldDataGood.push(this.state.provider[0].Action);
oldDataGood.push(this.state.provider[0].Anime);
oldDataGood.push(this.state.provider[0].Children);
}
render() {
console.log(this.state.musicalData);
const options = {
responsive: true,
maintainAspectRatio: false,
legend: {
display: true
},
type: "bar",
scales: {
xAxes: [
{
stacked: true
}
],
yAxes: [
{
stacked: true
}
]
}
};
return (
<div>
<Bar data={this.state.data} height={300} options={options} />
</div>
);
}
}
export default Data;
you have to update the state after changing the datasets,
pushAction = () => {
const dataState = this.state.data;
const oldDataTotal = this.state.data.datasets[0].data;
const oldDataGood = this.state.data.datasets[1].data;
oldDataTotal.push(this.state.provider[0].huluAction);
oldDataTotal.push(this.state.provider[0].huluAnime);
oldDataTotal.push(this.state.provider[0].huluChildren);
oldDataGood.push(this.state.provider[0].Action);
oldDataGood.push(this.state.provider[0].Anime);
oldDataGood.push(this.state.provider[0].Children);
this.setState({data: {...dataState, datasets : [...oldDataTotal, oldDataGood]}});
}

Highcharts not rendering : React+Typescript+Highcharts

Trying to bring up a highchart using react. I am having multiple fetch api calls(for illustration, I have added only 2) whose data I will be using to render something in the UI.
In this example data1 is used to render a table, data2 is used to render a highchart.
I am storing the outputs of these calls in a state object. When I am calling these API's, I am getting the data but unable to set it to "series" property of highcharts for rendering, as a result nothing is getting rendered.
Structure of the data I am fetching
"api2" : [
{
"name" : "Test1",
"value" : 12
},
{
"name" : "Test2",
"value" : 9
}
]
Can someone help me with this? Where am I going wrong?
I am using highcharts-react-official for this
Code
import * as React from 'react';
import Highcharts from 'highcharts'
import HighchartsReact from 'highcharts-react-official';
interface IState {
data1: [];
data2: [];
}
interface IProps {}
class Example extends React.Component<IProps,IState> {
constructor(props:any)
{
super(props);
this.state = {
data1: [],
data2: []
}
}
componentDidMount()
{
Promise.all([
fetch('http://localhost:3001/api1'),
fetch('http://localhost:3001/api2')
])
.then(([res1, res2]) => Promise.all([res1.json(), res2.json()]))
.then(([data1, data2]) => this.setState({
data1: data1,
data2: data2
}));
}
render() {
let options:any;
options = {
chart: {
type: 'column'
},
credits: false,
exporting: {enabled: false},
title: {
text: ''
},
legend: {
layout: 'vertical',
align: 'right',
verticalAlign: 'bottom'
},
xAxis: {
visible:false
},
yAxis: {
visible:false
},
plotOptions: {
column: {
dataLabels: {
enabled: true }
}
},
series: this.state.data2
};
return(
<div className="an-content">
//some table rendering will happen here
<HighchartsReact
highcharts={Highcharts}
options={options}
/>
</div>
)
}
}
export default Example;
You need to provide the data format required by Highcharts:
this.setState({
data2: data2.map(x => ({ name: x.name, data: [x.value] }))
});
Live demo: https://codesandbox.io/s/7w2pw4p900
API Reference: https://api.highcharts.com/highcharts/series.column.data

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