I have a Table from react-bootstrap that displays a list of devices with several columns. I want to change this so you can reorder by the columns so I switched to BootstrapTable from react-bootstrap-table-next. But my problem is that changing the state of the Component doesn't cause the table to update - this only happens if I click on a column to reorder the table.
The code is written to create the devices_table and save it to state then call an API to get the device version and add that to the state, causing the component to redraw. But when render() is called again the additional data isn't added to the table.
I've created a working example at https://codesandbox.io/s/react-bootstrap-table-next-new-data-problem-5w0op
import React, { Component } from 'react'
import BootstrapTable from 'react-bootstrap-table-next'
class DeviceTable extends Component {
constructor(props) {
super(props)
this.state = {
devices_table: {}
}
}
componentDidUpdate(prevProps) {
if (prevProps.devices !== this.props.devices) {
let devices_table = this.props.devices.map(this.myFunction);
this.setState({
devices_table: devices_table
})
}
}
myFunction = (value, index, array) => {
let device = {};
device.device = value;
device.index = index + 1;
device.version = '';
apiGetDeviceVersion(value.identifier)
.then((res, deviceId = value.identifier) => {
let devices_table = this.state.devices_table;
let objIndex = devices_table.findIndex(d => d.device.identifier === deviceId)
devices_table[objIndex].version = res.valueReported;
this.setState({
devices_table: devices_table
})
})
.catch(e => {
console.log(e)
})
return device;
}
render() {
const devices = this.state.devices_table;
if (isEmpty(devices)) {
return (<div></div>)
}
let columns = [
{
text: "#",
dataField: "index",
sort: true
},
{
text: "ID",
dataField: "device.identifier",
sort: true
},
{
text: "Name",
dataField: "device.name",
sort: true
},
{
text: "Status",
dataField: "device.status",
sort: true
},
{
text: "Version",
dataField: "version",
sort: true
}
];
return (
<div>
<BootstrapTable keyField={"device.identifier"} data={devices} columns={columns}></BootstrapTable>
</div>
)
}
}
export default DeviceTable
componentDidUpdate(prevProps) {
if (prevProps.devices !== this.props.devices) {
*let devices_table* = this.props.devices.map(this.myFunction);
this.setState({
devices_table: devices_table
})
}
}
It may happen due to response is working as asynchronous function(because of API). Thus, you have to wait for the response to make it available, otherwise "devices_table" remains empty or undefined and leads to no data visualization.
Related
I need to use a custom function in Material-Table column render property.
The function gets called, I get printed on the console the expected results, however, the result would simply not render in the table.
Here is the code:
import React from 'react';
import HraReferenceDataContext from '../context/hraReferenceData/hraReferenceDataContext';
import MaterialTable from 'material-table';
const EmployeeDetailsCompanyDocuments = ({ companyDocumentsData }) => {
const hraReferenceDataContext = React.useContext(HraReferenceDataContext);
const { companyDocumentTypes } = hraReferenceDataContext;
const getDocumentTypeForRow = id => {
companyDocumentTypes.forEach(type => {
if (type.id === id) {
console.log(type.name)
return type.name;
}
});
};
const columnInfo = [
{
field: 'typeId',
title: 'Type',
render: rowData =>{ getDocumentTypeForRow(rowData.typeId)}, //here is the problem
},
{ field: 'created', title: 'Created On' },
];
return (
<MaterialTable
columns={columnInfo}
data={companyDocumentsData}
title="Company Documents List"
/>
);
};
Returning inside forEach doesn't work.
change this function
const getDocumentTypeForRow = id => {
companyDocumentTypes.forEach(type => {
if (type.id === id) {
console.log(type.name)
return type.name;
}
});
};
to
const getDocumentTypeForRow = id => {
return companyDocumentTypes.find(type => type.id === id).name;
};
update
change
render: rowData =>{ getDocumentTypeForRow(rowData.typeId)},
to
render: rowData => getDocumentTypeForRow(rowData.typeId),
because you should return the value that is returned from getDocumentTypeForRow.
i am getting this response from back-end like this. how can i take this response in to state variable and how can i use the dynamic value to pass in this chart inside the data:[[ ]]array in reactjs. i am using in my code like this format. i need only dynamic value in this chart based on select the dropdown list select the one value(last day) and click on applying filter button. am sending the request and getting this (last day) response right. i need whatever value getting the response. i need to show dynamic value in this chats. based on applying the filter. how to do pass dynamic value in this data:[[]] array.
class Report extends React.Component {
constructor(props) {
super(props);
this.state = {
dashboardchart:{
series:[{
data:[]
}],
},
};
}
componentDidMount() {
UserAction._getdashboardchart();
}
_userStoreChange(type, value) {
if (type == 'DashboardChart') {
let dashboardchart = UserStore._getdashboardchart() || {};
this.setState({dashboardchart})
}
}
render() {
let chartdata = this.state.dashboardchart.series
const piechart = {
chart: {
type: 'pie',
options3d: {
enabled: true,
alpha: 45
}
},
title: {
text: 'Claim - Key Performance Indicators'
},
subtitle: {
text: ''
},
plotOptions: {
pie: {
innerSize: 100,
depth: 45
}
},
series: [{
name: 'Total consumption',
data: chartdata && chartdata.data && chartdata.data
}]
}
return (
<div className="panel-body">
<ReactHighcharts config={piechart}></ReactHighcharts>
</div>
);
}
}
export default Report;
I'm trying to update my chart using react-chartjs-2. I'm using date picker to filter different data and re-render the chart accordingly such as display data today, yesterday, last 7 days etc.. The data is being fetched from my database
However, when the chart gets redrawn, and re-rendered, it gets added onto the state which I don't want. I simply want to re-render the new data that was requested not re-render and add onto the old data that was in the chart.
I actually fixed this issue before with vanilla JavaScript because I wasn't using react I used the destroy() method that the chart documentation says to use but i'm not sure how to use it with react?
So upon further inspection, it seems my chart is re-rendering fine. However, when it gets re-rendered, additional data is being added onto my chartData state which I don't want, I just want the new data that got requested to show on the chart. I'm still trying to figure that portion out.
Here is my code I have a lot of it so I'll only show the relevant parts:
import React from "react";
import reportsService from "../../services/reportsService";
import update from "react-addons-update";
import moment from "moment";
import { Bar } from "react-chartjs-2";
import "chartjs-plugin-labels";
import "chartjs-plugin-datalabels";
class Reportspage extends React.Component {
constructor(props) {
super(props);
this.state = {
chartData: {
labels: [],
datasets: [
{
//label: "Quotes",
data: [],
backgroundColor: []
}
]
}
};
}
chartColors() {
let colors = [];
for (let i = 0; i < 100; i++) {
let r = Math.floor(Math.random() * 200);
let g = Math.floor(Math.random() * 200);
let b = Math.floor(Math.random() * 200);
let c = "rgb(" + r + ", " + g + ", " + b + ")";
colors.push(c);
}
// Update deep nested array of objects in state
this.setState({
chartData: update(this.state.chartData, {
datasets: { 0: { backgroundColor: { $set: colors } } }
})
});
}
datePicker() {
let _this = this;
let start = moment().subtract(29, "days");
let end = moment();
let showReports;
let data;
let reloNames = [];
let reloCount = [];
function focusDate(start, end) {
$("#daterangePicker span").html(
start.format("MMMM D, YYYY") + " - " + end.format("MMMM D, YYYY")
);
}
$("#daterangePicker").daterangepicker(
{
startDate: start,
endDate: end,
ranges: {
Today: [moment(), moment()],
Yesterday: [
moment().subtract(1, "days"),
moment().subtract(1, "days")
],
"Last 7 Days": [moment().subtract(6, "days"), moment()],
"Last 30 Days": [moment().subtract(29, "days"), moment()],
"This Month": [moment().startOf("month"), moment().endOf("month")],
"Last Month": [
moment()
.subtract(1, "month")
.startOf("month"),
moment()
.subtract(1, "month")
.endOf("month")
]
}
},
focusDate
);
focusDate(start, end);
$("#daterangePicker").on("apply.daterangepicker", async function(
event,
picker
) {
switch (picker.chosenLabel) {
case "Today":
showReports = await reportsService.reloQuotes({
reportStatus: "Today"
});
data = showReports.recordsets[0];
data.forEach((element, index, array) => {
reloNames.push(element.reloNames);
reloCount.push(element.NoofOrders);
});
_this.setState({
chartData: update(_this.state.chartData, {
labels: { $set: reloNames },
datasets: { 0: { data: { $set: reloCount } } }
})
});
console.log(_this.state);
break;
case "Yesterday":
showReports = await reportsService.reloQuotes({
reportStatus: "Yesterday"
});
data = showReports.recordsets[0];
data.forEach((element, index, array) => {
reloNames.push(element.reloNames);
reloCount.push(element.NoofOrders);
});
_this.setState({
chartData: update(_this.state.chartData, {
labels: { $set: reloNames },
datasets: { 0: { data: { $set: reloCount } } }
})
});
console.log(_this.state);
break;
case "Last 7 Days":
showReports = await reportsService.reloQuotes({
reportStatus: "Last 7 Days"
});
data = showReports.recordsets[0];
data.forEach((element, index, array) => {
reloNames.push(element.reloNames);
reloCount.push(element.NoofOrders);
});
_this.setState({
chartData: update(_this.state.chartData, {
labels: { $set: reloNames },
datasets: { 0: { data: { $set: reloCount } } }
})
});
console.log(_this.state);
break;
}
});
//console.log(this.state);
}
async reloQuotes() {
const showreloQuotes = await reportsService.reloQuotes();
let data = showreloQuotes.recordsets[0];
let reloNames = [];
let reloCount = [];
data.forEach((element, index, array) => {
reloNames.push(element.reloNames);
reloCount.push(element.NoofOrders);
});
this.setState({
chartData: update(this.state.chartData, {
labels: { $set: reloNames },
datasets: { 0: { data: { $set: reloCount } } }
})
});
}
async componentDidMount() {
await this.chartColors();
await this.datePicker();
// Execute models real time thus re-rendering live data on the chart real time
await this.reloQuotes();
}
render() {
return (
<div className="fluid-container">
<div className="container">
<h1>Reports</h1>
<div className="row">
<div className="daterangeContainer">
<div
id="daterangePicker"
style={{
background: "#fff",
cursor: "pointer",
padding: "5px 10px",
border: "1px solid #ccc",
width: "100%"
}}
>
<i className="fa fa-calendar" />
<span /> <i className="fa fa-caret-down" />
</div>
</div>
</div>
<div className="row">
<div className="col-md-12">
<Bar
data={this.state.chartData}
height={800}
options={{
maintainAspectRatio: false,
legend: {
display: false
},
scales: {
xAxes: [
{
ticks: {
beginAtZero: true,
autoSkip: false
},
scaleLabel: {
display: true
}
}
]
},
title: {
display: true,
text: "Quotes",
fontSize: 16
},
plugins: {
datalabels: {
display: true,
color: "white"
}
}
}}
redraw
/>
</div>
</div>
</div>
</div>
);
}
}
export default Reportspage;
So the problem that you describe is that data is added to your component state when the component is re-rendered. In the code snipped you have supplied, you dont use any of the life cycle methods in React that can be triggered on re-renders. And I cannot see any other hooks that should trigger on a re-render. Therefore I cant find any source to your problem.
However, I can see other issues that might make debugging harder for you. Solving these might help you in nailing down the actual problem. In the componentDidMount method you call functions whose only purpose is to update the state. This is not good design since it will force the component to immediately re-render several times whenever it is mounted.
A better design is to fully prepare the chartData-objekt in the constructor. For example, you could change your chartColors function to take a chartData object as a parameter, and return the new object with colors added. Then make your constructor look something like this:
constructor(props) {
super(props);
const chartDataWithoutColors = {
labels: [],
datasets: [
{
//label: "Quotes",
data: [],
backgroundColor: []
}
]
}
const chartDataWithColor = this.chartColors(chartDataWithoutColors);
this.state = {
chartData: chartDataWithColor
};
}
By removing unnecessary calls to setState you will make your components life both simpler and more performant. When you have simplified your component, start debugging by removing non-critical parts one at the time and try to nail down when your problem disappears. This should be sufficient to find the bug.
Good luck!
import React, { Component } from 'react';
import { connect } from 'react-redux';
import getSchoolsList from '../Actions/Index';
import ReactTable from "react-table";
import checkboxHOC from "react-table/lib/hoc/selectTable";
import "react-table/react-table.css";
const CheckboxTable = checkboxHOC(ReactTable);
class Home extends Component {
constructor(props){
super(props);
this.state = {
selection: [],
selectAll: false
};
}
componentDidMount(){
this.props.getSchoolsList();
}
toggleSelection = (key, shift, row) => {
let selection = [...this.state.selection];
const keyIndex = selection.indexOf(key);
if (keyIndex >= 0) {
selection = [
...selection.slice(0, keyIndex),
...selection.slice(keyIndex + 1)
];
} else {
selection.push(key);
}
this.setState({ selection });
};
toggleAll = () => {
const selectAll = this.state.selectAll ? false : true;
const selection = [];
if (selectAll) {
const wrappedInstance = this.checkboxTable.getWrappedInstance();
const currentRecords = wrappedInstance.getResolvedState().sortedData;
currentRecords.forEach(item => {
selection.push(item._original._id);
});
}
this.setState({ selectAll, selection });
};
isSelected = key => {
console.log(key);
return this.state.selection.includes(key);
};
logSelection = () => {
console.log("selection:", this.state.selection);
};
render() {
const { toggleSelection, toggleAll, isSelected, logSelection } = this;
const { selectAll } = this.state;
const checkboxProps = {
selectAll,
isSelected,
toggleSelection,
toggleAll,
selectType: "checkbox",
};
const data = this.props.StateData?this.props.StateData.data:[];
const {loading, StateData} = this.props;
if (loading) {
{console.log(loading)}
return <div>Loading...</div>;
}
return (
<div>
{console.log(this.checkboxTable)}
<button onClick={logSelection}>Log Selection</button>
<CheckboxTable
ref={r => (this.checkboxTable = r)}
data={data}
columns={[
{
Header: "School Name",
accessor: "name"
},
{
Header: "Location",
id: "lastName",
accessor: d => d.area + ',' + d.city
},
{
Header: "Curriculum",
accessor: "curriculum"
},
{
Header: "Grade",
accessor:"grade"
},
{
Header: "Web App_URL",
accessor: "webapp_url",
},
{
Header: "Status",
id: "status",
accessor: d =>{
if(d.publish === true){
console.log(d.publish)
return 'Publish';
}else{
return 'Unpublished'
}
}
}
]}
defaultPageSize={10}
className="-striped -highlight"
{...checkboxProps}
/>
</div>
);
}
}
function mapStateToProps (state) {
return {
StateData:state.login.schools,
loading: state.login.loading,
}
};
export default connect(mapStateToProps, {getSchoolsList})(Home);
Hi all, can someone help me with this what is the wrong i am not getting individual checkboxes in this ? i checked this link code in my local it is working <https://codesandbox.io/s/7yq5ylw09j?from-embed>, but whenever i add my dynamic data it is not working.
Hi all, can someone help me with this what is the wrong i am not getting individual checkboxes in this ? i checked this link code in my local it is working <https://codesandbox.io/s/7yq5ylw09j?from-embed>, but whenever i add my dynamic data it is not working.
Hi all, can someone help me with this what is the wrong i am not getting individual checkboxes in this ? i checked this link code in my local it is working https://codesandbox.io/s/7yq5ylw09j?from-embed, but whenever i add my dynamic data it is not working.
If your using TypeScript and tslint this happens via the example for select table(checkboxes) getdata() does this:
const _id = chance.guid();
return {
_id,
...item
};
tslint complains about the _id var naming with "variable name must be in lowerCamelCase, PascalCase or UPPER_CASE"
You can see that at: https://react-table.js.org/#/story/select-table-hoc
So you have to change _id to id if you want to get past tslint. Changing from _id to id breaks the default keyField logic in react-table which wants _id. That necessitates setting the keyField property to "id".
If you do not mention unique key id by default it will take "_id" as the key field. By defining a key value you can overcome the above mentioned matter as follows.
Let's say there is a specific column named "USER ID". And we'll take the accessor of the column as "uid".
The code should be modified as follows.
Checkbox Table
<CheckboxTable
keyField="uid"
......Rest of your code....
/>
toggleAll()
toggleAll() {
..........code...........
currentRecords.forEach(item => {
selection.push(item.uid);
});
}
.......code............
}
I am working with React and HighCharts. I am relatively new to both these technologies. I need to generate two synchronized HighStock charts. I was able to display the charts with the below layout.
<div class=container>
<div class=chart1>new highcharts.StockChart(newChartOptions) </div>
<div class=chart2>new highcharts.StockChart(newChartOptions)</div>
</div>
The Charts are displayed. I want to synchronize the charts to have a common tool tip, I see the http://www.highcharts.com/demo/synchronized-charts , not sure how to implement with React. I have tried to assign a function(handleEvent(e)) to plotOptions:{line:{ point:{ event:{click: and MouseOver}}}} but it did not help. Not sure how to invoke the handleEvent(e) method. I am not sure how/when to invoke the handleEvent(e). Any help is greatly is appreciated.
Below is the Component code:
import $ from 'jQuery';
import React from 'react';
import highcharts from 'highcharts-release/highstock';
export default class SynchronizedStatusChart extends React.Component {
constructor (props) {
super(props);
this.state = {
chartName: `chart${this.props.chartNum}`,
};
}
handleEvent(e){
let allCharts = highcharts.charts;
console("SynchronizedStatusChart:handleEvent:ChartsLength = " + allCharts.length);
var chart, point, i, event;
for (i = 0; i < allCharts.length; i = i + 1)
{
chart = highcharts.charts[i];
event = chart.pointer.normalize(e.originalEvent); // Find coordinates within the chart
point = chart.series[0].searchPoint(event, true); // Get the hovered point
if (point) {
this.onMouseOver(); // Show the hover marker
this.series.chart.tooltip.refresh(this); // Show the tooltip
this.series.chart.xAxis[0].drawCrosshair(event, this);
}
}
}
componentDidMount () {
}
componentWillUpdate (nextProps) {
for(let i=0; i<nextProps.data.length; i++){
this.generateChart(nextProps.data[i],i+1,nextProps.titles[i]);
}
}
generateChart(data, i, title) {
if(data == null)
{
data = [];
}
let ticksData = [0,1];
let newChartOptions =
{
chart: {
//renderTo: document.getElementById(this.state.chartName),
renderTo: document.getElementById(`SyncChart${i}`),
height:'125'
},
rangeSelector: {
enabled: false
},
credits: {
enabled: false
},
navigator: {
enabled: false
},
scrollbar: {
enabled: false
},
tooltip: {
shared: true,
pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y:.2f}</b> <br/>'
},
xAxis:{
},
yAxis: {
offset: 15,
labels: {
align: 'center',
x: -3,
y: 6
},
tickPositioner: function () {
var positions = ticksData;
return positions;
},
opposite:false,
showLastLabel: true,
title:{
text:title
}
},
series: [{
name: title,
type: this.props.status ? 'area' : 'line',
data: data,
showInNavigator: false
}],
};
new highcharts.StockChart(newChartOptions);
}
render () {
return (
<div className="med-chart col-md-9" id={this.state.chartName} style={this.props.chartStyle}>
<div id='SyncChart1'></div>
<div id='SyncChart2'></div>
</div>
);
}
}
I had the same problem recently. Here's what has worked for me.
I am using common parent component to add pure javascript event listeners for 'mousemove' and 'mouseleave' to each chart DOM element.
class ParentComponent extends Component {
...
componentDidMount() {
this.charts = document.querySelectorAll('.chart-container');
[].forEach.call(this.charts, (chart) => {
chart.addEventListener('mousemove', this.handleChartMouseMove);
chart.addEventListener('mouseleave', this.handleChartMouseLeave);
});
Highcharts.Pointer.prototype.reset = () => undefined;
}
componentWillUnmount() {
[].forEach.call(this.charts, (chart) => {
chart.removeEventListener('mousemove', this.handleChartMouseMove);
chart.removeEventListener('mousemove', this.handleChartMouseLeave);
});
}
handleChartMouseMove = (e) => {
const chartIndex = e.currentTarget.dataset.highchartsChart;
const chart = Highcharts.charts[chartIndex];
const event = chart.pointer.normalize(e);
const pointIndex = chart.series[0].searchPoint(event, true).index;
Highcharts.charts.forEach((chart) => {
const xAxis = chart.xAxis[0];
const point = chart.series[0].points[pointIndex];
const points = chart.series.map(s => s.points[pointIndex]); // if more than one series
point.onMouseOver();
xAxis.drawCrosshair(event, point);
// if more than one series, pass an array of points, took a me a long time to figure it out
chart.tooltip.refresh(points, event);
});
};
handleChartMouseLeave = () => {
Highcharts.charts.forEach((chart) => {
chart.tooltip.hide();
chart.xAxis[0].hideCrosshair();
});
};
...
}