ReactJs calculate sum of all values present in Table column - reactjs

I am having a Bootstrap-table rendering values from service called in componentDidMount().
Example of my table -
Col1 Col2 Col3
1 2 3
4 5 6
7 8 9
SumValue 15 18 //This last row holds sum of all values
How to calculate SumValue of all the values present in col1 and display in Footer.
Below is the code how i am using react-row for mapping data to rows.
And value is the variable holding data of all columns present in json file returned from service, after setting it to the component's state.
{this.state.value && this.state.value.map(function (value, ind) {
return <Row key={ind} item={value}></Row>
})
}
Initializing state
constructor(props){
super(props)
{
this.state ={
value: [], //Initializing an array
SumValue: '',
default: false,
}
}
Setting state
fetch('https://www.serviceUrl.com')
.then(res => res.json())
.then(value => {
this.setState({
value: value.empRecords, //Here its getting all records from json
default: false
});
})
Let me know guys if any more information is required.

I would get the sum using reduce:
const SumValue = this.state.value && this.state.value.reduce((a, v) => a + v, 0)

1) initial columnNames and array list
state = {
callList: [],
columnModel: [
{ columnName: "date" },
{ columnName: "totalCalls" },
{ columnName: "answeredCalls" },
{ columnName: "abandonedCalls" },
{ columnName: "abandonRate" },
{ columnName: "avgAnswerSpeed" },
]
};
2) Get data from api and prepare array data
try {
const { errors, list, success } = (await apiService.getCalls(request)).data;
if (success) {
// first list is normal numbers count only,
// if you want count avg sum for some columns send second param array list and include column names
// now i want count avg for 'abandonRate', 'avgAnswerSpeed' , others just count sum
this.setState({
callList: list,
callListSum: this.sumCount(
list,
['abandonRate', 'avgAnswerSpeed']
)
})
}
} catch (error) {
console.log(error);
}
sumCount = (list = [], avgColumns = []) => {
const sum = {};
// count numbers
list.map((item) => {
Object.entries(item).map(([key, val]) => {
if (typeof (val) === 'number') {
sum[key] = sum[key] ? (sum[key] + val) : val;
}
})
});
// count avg
avgColumns.map(key => {
if (sum[key]) {
sum[key] = sum[key] / list.length;
}
})
return sum;
}
3) Render data
<table>
<thead>
<tr style={{ backgroundColor: "#F5F7FA" }}>
{
this.state.columnModel.map((item, i) =>
<th key={i}> {item.columnName}</th>
)
}
</tr>
</thead>
<tbody>
{
this.state.callList.map(
(info, index) => (
<tr
key={index}
>
{
this.state.columnModel.map((item, i) =>
(
<td key={i}>
{info[item.columnName]}
</td>
)
)
}
</tr>
)
)}
{/* Render sum area */}
<tr
style={{ backgroundColor: "#F5F7FA" }}
>
{
this.state.columnModel.map((item, i) =>
(
<td style={{ fontWeight: "bold" }} >
{this.state.callListSum[item.columnName]}
</td>
)
)
}
</tr>
</tbody>
</table>

Related

Why when i try run this code in reactjs, its always output wrong total number

i still newbie in reactjs, and i try to build a some project. this project its to display any product price and stock, but here this problem.
i try to sum this number to get a total price in my product in reactjs, but the output always like this. how to fix that...
import React from "react";
import { nanoid } from "nanoid";
import calc from "./calc";
export default class TableData extends React.Component {
constructor(props) {
super(props);
}
render() {
let arr = []
const {
data,
} = this.props;
const {
location, proformaItem,
} = data;
proformaItem.forEach((item) => {
const parseStock = JSON.parse(item.product_stock);
parseStock.forEach((stock) => {
let total = 0
if (stock[1] !== undefined) {
total += Number(stock[1]);
}
if (stock[5] !== undefined) {
total += Number(stock[5])
}
arr.push(total);
})
})
console.log(arr);
return (
<>
{
proformaItem.map((item, index) => {
const parseStock = JSON.parse(item.product_stock);
const parseItem = JSON.parse(item.items);
return (
<tr key={nanoid(12)}>
<td key={nanoid(12)}>{parseStock.map((key) => key[1])}</td>
<td key={nanoid(12)}>{parseStock.map((key) => key[3])}</td>
<td key={nanoid(12)}>{parseStock.map((key) => key[5])}</td>
<td key={nanoid(12)}>{item.categoryDescription}</td>
<td key={nanoid(12)}>{item.productDescription}</td>
<td key={nanoid(12)}>{
parseStock.map((item) => {
if (item[1] !== undefined && item[5] !== undefined) {
console.log(calc(item[1], item[5]));
}
})
}
</td>
<td key={nanoid(12)}>1</td>
<td key={nanoid(12)}>{parseItem.map((key) => key['qty'])}</td>
</tr>
)
})
}
</>
)
}
}
here's my data in json
{
"proformaItem": [
{
"product_id": "1852",
"productDescription": "Charcoal Size M",
"categoryDescription": "7200 Premium Tee",
"product_stock": "[{\"1\": 272}, {\"3\": 5328}, {\"5\": 177}]",
"items": "[{\"qty\": 1, \"productId\": 1852, \"proformaInfoId\": 556745, \"proformaItemId\": 2679283}]"
},
{
"product_id": "1801",
"productDescription": "Black Size S",
"categoryDescription": "7200 Premium Tee",
"product_stock": "[{\"1\": 745}, {\"3\": 30744}, {\"5\": 273}]",
"items": "[{\"qty\": 1, \"productId\": 1801, \"proformaInfoId\": 556745, \"proformaItemId\": 2679284}]"
},
]
}
enter image description here
The problem is you changing arr array in the render method, it means it pushes every time the component renders, that's why you are getting unstable results. To avoid this, you could move this logic
proformaItem.forEach((item) => {
const parseStock = JSON.parse(item.product_stock);
parseStock.forEach((stock) => {
let total = 0
if (stock[1] !== undefined) {
total += Number(stock[1]);
}
if (stock[5] !== undefined) {
total += Number(stock[5])
}
arr.push(total);
})
})
to componentDidMount() callback, for example.
Also you shouldn't use key={nanoid(12)}, because it creates the new keys every time it renders, but key should be strong and stable. It's better to use your product it: key={item.product_id}
UPD: if you want just to sum up your total, you don't need to use arr at all, you could use just total:
let total = 0
proformaItem.forEach((item) => {
const parseStock = JSON.parse(item.product_stock);
parseStock.forEach((stock) => {
if (stock[1] !== undefined) {
total += Number(stock[1]);
}
if (stock[5] !== undefined) {
total += Number(stock[5])
}
})
})
console.log(total)

avoid Too many re-renders, circular re-rendering

I've got a table, and each row (DifferentialRow) of the table queries for its own data with react-query's useQuery hook.
I want the table's rows to be sorted by a given field value (calculated within DifferentialRow), and also only show the top 10.
<Table.Body>
{filteredVariables!.map((variable, i) => {
return variable.show ? (
<DifferentialRow
key={i}
....
variable={variable.variable}
setFilteredVariables={setFilteredVariables}
/>
) : null;
})}
</Table.Body>
So when a (DifferentialRow) row has retrieved its data and calculated the sort value, I update the parent filteredVariables object with the new row value, sort, and then set show = true for the top 10 using setFilteredVariables which is passed into DifferentialRow (all shown below).
const diffQuery = useQuery(["differential", {startDate, variable}],fetchDifferentialData);
...
if (diffQuery.isSuccess && diffQuery.data) {
setSortValue(calcSortValue(diffQuery.data.data));
}
html rows here
...
function calcSortValue(resultData: any[]) {
// once we've got a result, and we have calculated the diff we need
// to set the filteredVariables object that keeps track of cumulative data to only show top x
try {
let sortValue = resultData[0].dataValue - resultData[numberOfDays - 1].dataValue;
setFilteredVariables((prev: { variable: string; diff: number; show: boolean }[]) => {
let newResults = [...prev, { diff: sortValue, variable, show: undefined }];
newResults.sort((a, b) => {
return Math.abs(b.diff || 0) - Math.abs(a.diff || 0);
});
let inTopTen = newResults
.slice(0, 10)
.map((co) => co.variable)
.includes(variable);
let finalResults: CompareObject[];
if (inTopTen) {
finalResults = newResults.map((nr) => {
return nr.variable === variable? { ...nr, show: true }: nr;
});
} else {
finalResults = newResults.map((nr) => {
return nr.variable === variable? { ...nr, show: false }: nr;
});
}
return finalResults;
});
return diff;
} catch (error) {
return 0;
}
}
This is all creating circular re-rendering, and I can't figure out how to get around it.
Okay, so my solution was to completely remove the DistributionRow component and query for the data in the parent (table) component using useQueries (each query representing a row). Then I do all the sorting and slicing on the result from useQueries.
const diffResults = useQueries(...)
return diffResults.some((dr) => dr.isSuccess) ? .... <Table.Body>
{diffResults
.filter((dr) => dr.isSuccess)
.map((dr: any) => {
let dateSorted = dr.data.data.sort(function (a: any, b: any) {
return new Date(b.runDate).getTime() - new Date(a.runDate).getTime();
});
let diff = Math.round(calcDiff(dateSorted) * 10) / 10;
let fullResult = {
results: dr.data,
diff,
};
return fullResult;
})
.sort((a, b) => {
return Math.abs(b.diff || 0) - Math.abs(a.diff || 0);
})
.slice(0, 10)
.map(({ diff, results }, i) => {
return (
<Table.Row key={i} data-testid="table-row">
<Table.Cell>{results.variable}</Table.Cell>
{results.data.map((d: any, i: number) =>
new Date(d.runDate) > endDate ? (
<Table.Cell key={i}>
{isNaN(d?.dataValue) ? null : Math.round(d?.dataValue * 10) / 10}
</Table.Cell>
) : null
)}
{compareColumn ? <Table.Cell>{diff}</Table.Cell> : null}
</Table.Row>
);
})}
</Table.Body>

How to set sorting column state as "asc" after click on new column and change sorting state "asc": "desc" during clicking on selected column in React

I have a problem with sorting state.
I would like to set column sorting as ascending every time when I change the selection of my column and change state as "asc" then "desc" etc.
When I clicking on the same column. Method getHeaderName get actually selected header name and it works properly. Method sortedMethod
just have sorting mechanism(works good) and set state of column name previously selected and actually selected:
private sortedMethod(columnName: HeaderNameDictionary) {
this.sortedMechanism(columnName);
this.setState({ previousColumn: this.state.sortedColumnName })
this.setState({ sortedColumnName : columnName})
}
I think that the problem is with the method "setStateSelectedColumn". In this method, I check the state of current and previous column names and depend on it I set "isAscending" state. But it does not work well.
private setStateSelectedColumn(columnName: HeaderNameDictionary) {
if ((this.state.previousColumn !== this.state.sortedColumnName )) {
this.setState({ isAscending: true })
} else {
this.setState({ isAscending: !this.state.isAscending })
}
}
When I change column and click every time on another column it works good and
change state as isAsc: true,
but when I :
change column - isAsc: true,
Click one more time on this column - isAsc: true,
Click one more time on this column - is Asc: false,
Click one more time on this column - is Asc: true,
Click one more time on this column - is Asc: false,
Second clicking on the same column is problematic and does not change the state.
<th>
Product Code
<span
className={this.getHeaderName(HeaderNameDictionary.PRODUCT_CODE)}
onClick={() => {
this.setState({}, () => {
this.setStateSelectedColumn(HeaderNameDictionary.PRODUCT_CODE);
});
this.sortedMethod(HeaderNameDictionary.PRODUCT_CODE);
}}
/>
</th>;
Do you have any idea how to fix this? Thanks!
It's better to use the setState overload that takes a function instead of the object.
Here is a simple example.
class Sample extends React.Component {
constructor() {
super();
this.initData = [
{name: 'John', family: 'Doe'},
{name: 'Jane', family: 'Hardy'}
];
this.state = {
sortField: 'name',
sortOrder: 'asc',
data: this.initData
};
}
sort(column) {
this.setState(prevState => {
if (prevState.sortField === column) {
const sortOrder = prevState.sortOrder === 'asc' ? 'desc' : 'asc';
return {
sortOrder,
data: [...prevState.data].sort((a, b) => {
const textA = a[column].toUpperCase();
const textB = b[column].toUpperCase();
if (sortOrder === 'asc') {
return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
}
else {
return (textA > textB) ? -1 : (textA < textB) ? 1 : 0;
}
})
}
}
else {
return {
sortOrder: 'asc',
sortField: column,
data: [...prevState.data].sort((a, b) => {
const textA = a[column].toUpperCase();
const textB = b[column].toUpperCase();
return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
})
}
}
})
}
render() {
return (
<table>
<thead>
<th onClick={() => this.sort('name')}>Name</th>
<th onClick={() => this.sort('family')}>Family</th>
</thead>
<tbody>
{
this.state.data.map(({name, family}, i) => {
console.log(name, family)
return (
<tr key={i}>
<td>{name}</td>
<td>{family}</td>
</tr>
)
})
}
</tbody>
</table>
)
}
}
React.render(<Sample />, document.getElementById('app'));

React.js: Unable to access object attribute within state object

I am working on a finance tracker. Eventually, the finance tracker will be able to access an account in the user's bank account, pull every transaction, and the user will be able to add transactions as future projections. The idea is to give the user the ability to run financial projections/scenarios using the most recent checking/saving account information in the user's bank account(s).
I am working on a "running total" column which takes the amount found in transactionData.amount and will add transactionData.amount to startBal if this is the "zero" index. Otherwise, it will use the numbers found in the previous index for transactionData.runningTotal and add to the value found in the current index for transactionData.amount.
In either case, the new calculation should be added to the current index for transactionData.runningTotal. I am essentially mimicking what an online transaction detail would, in the event that the bank does not provide this data already.
Here is the parent component.
import React, { Component } from "react";
import TransactionSearch from "./transactionSearch.js";
import PendingTransactions from "./pendingTransactions.js";
import Transactions from "./transactions.js";
class CheckingAccount extends Component {
state = {
startBal: 1000,
pendingTransData: [
{ id: 0, date: "1/1/2020", transaction: "gas", amount: -25.45 },
{ id: 1, date: "1/2/2020", transaction: "cell phone", amount: -127.35 },
{ id: 2, date: "1/3/2020", transaction: "car payment", amount: -303.97 }
],
transactionData: [
{
id: 0,
date: "1/1/2020",
transaction: "gas",
amount: -35.45,
runningTotal: null
},
{
id: 1,
date: "1/2/2020",
transaction: "cell phone",
amount: -227.35,
runningTotal: null
},
{
id: 2,
date: "1/3/2020",
transaction: "car payment",
amount: -403.97,
runningTotal: null
}
]
};
addRunningTotal() {
let { transactionData, startBal } = this.state;
console.log(transactionData);
transactionData.map((el, i) => {
console.log("in map function");
if (el[i] === 0) {
return (el[i].runningTotal = el[i].amount + startBal);
} else if (el[i] > 0) {
return (el[i].runningTotal = el[i - 1].amount + el[i].amount);
}
});
console.log("out of map function");
console.log("start Balance: ", startBal);
console.log("amount: ", transactionData[0].amount);
console.log("running total: ", transactionData[0].runningTotal);
this.setState({ transactionData: transactionData, startBal: startBal });
}
componentDidMount() {
this.addRunningTotal();
}
render() {
let pendTransData = (
<div>
<h1>PendingTransactions</h1>
<table>
<tr>
<th>Date</th>
<th>Transaction</th>
<th>Amount</th>
</tr>
</table>
{this.state.pendingTransData.map((pendingTransData, index) => {
return (
<PendingTransactions
key={pendingTransData.id}
date={pendingTransData.date}
transaction={pendingTransData.transaction}
amount={pendingTransData.amount}
/>
);
})}
</div>
);
let transData = (
<div>
<h1>Transaction Component</h1>
<table>
<tr>
<th>Date</th>
<th>Transaction</th>
<th>Amount</th>
<th>Running Total</th>
</tr>
</table>
{this.state.transactionData.map((transactionData, index) => {
return (
<Transactions
key={transactionData.id}
date={transactionData.date}
transaction={transactionData.transaction}
amount={transactionData.amount}
runningTotal={transactionData.runningTotal}
/>
);
})}
</div>
);
return (
<div className="App">
<h1> Checking Account</h1>
<TransactionSearch />
{pendTransData}
{transData}
</div>
);
}
}
export default CheckingAccount;
Here is the child component where the data should appear.
import React from "react";
function Transactions(props) {
return (
<tr>
<td>{props.date} </td>
<td>{props.transaction}</td>
<td>{props.amount}</td>
<td>{props.runningTotal}</td>
</tr>
);
}
export default Transactions;
First, runningTotal attribute does not render in the component. I expected to see a column with the new data in the runningTotal attribute.
In addRunningTotal, It looks like it's how you've used map. In map((el, i) => {}), el is a reference to the current iteration's value so where you've used el[i] (undefined), you wanted to use just el.
You also only need to use i (index) in your if statement.
This should do the trick (keeping reference to the previous value):
let prevAmount, running;
transactionData.map((el, i) => {
if (i === 0) {
running = el.runningTotal = el.amount + startBal;
prevAmount = el.amount;
return running;
} else if (i > 0) {
running = el.runningTotal = prevAmount + el.amount;
prevAmount = el.amount;
return running;
}
});

for my code iam not getting pagenation and searchbar in reactjs

//here is my code//
class TableList extends Component {
constructor(props) {
super(props);
//var totalPages = 100 / 10; // 10 page numbers
this.state = {
query: "",
countries: [],
searchString:[],
currentPageNumber: 1,
pageOfItems: [],
totalItems: 4,
itemsPerPage: 10
}
this.onChangePage = this.onChangePage.bind(this);
}
onChangePage(pageOfItems) {
this.setState({ pageOfItems: pageOfItems });
}
handleInputChange = (event) => {
this.setState({
query: event.target.value
},()=>{
this.filterArray();
})
}
handleSelect(number) {
console.log('handle select', number);
this.setState({currentPageNumber: number});
}
componentDidMount() {
const apiUrl = 'https://indian-cities-api-nocbegfhqg.now.sh/cities';
fetch(apiUrl)
.then(res => res.json())
.then(
(result) => {
this.setState({
countries: result,
searchString:result,
currentPageNumber:result.currentPageNumber,
totalItems: result.totalItems,
itemsPerPage: result.itemsPerPage
});
},
)
}
filterArray = () => {
let searchString = this.state.query;
let result = this.state.countries;
if(searchString.length > 0){
result = result.filter(searchString);
this.setState({
result
})
}
}
render() {
const { countries} = this.state;
let totalPages = Math.ceil(this.state.totalItems / this.state.numItemsPerPage);
return(
<div>
<div className="container">
</div>
<h2>countrie List</h2>
<form>
<input type="text" id="filter" placeholder="Search for..." onChange={this.handleInputChange}/>
</form>
<Table>
<Pagination
bsSize="medium"
items={totalPages}
activePage={this.state.currentPageNumber} onSelect={this.handleSelect.bind(this)}/>
<thead>
<tr>
<th>#ID</th>
<th>countrie Name</th>
<th>Code</th>
<th>States</th>
</tr>
</thead>
<tbody>
{countries.map(countrie => (
<tr key={countrie.City}>
<td>{countrie.sno}</td>
<td>{countrie.City}</td>
<td>{countrie.State}</td>
<td>{countrie.District}</td>
</tr>
))}
</tbody>
</Table>
</div>
)
}
}
export default TableList;
//the error coming is
Warning: Encountered two children with the same key, `Wadi`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version
IN SHORT:
Search - filtered data stored in this.state.result - not used in render
this.setState({
result
})
as { result } is a short of { result: result } and this is good ... overwriting this.state.countries would result in source data loss (needs refetching)
render gets/make use of this.state.countries - always full dataset, not filtered by search, not divided by page ranges
You need to copy some data into this.state.result after fetching (not copy a countries reference)
Pagination - 'results' (not proper as above) records not subselected by range based on currentPage
Inspect state changes (check if properly working) in browser using react dev tools.

Resources