Conditionally render <td> in a React component - reactjs

This is a JSX gist I'm displaying on the page.
rows.push(
<tr key={i}>
<td>{this.state['Person Name'] && this.state['Person Name'][i]}</td>
<td>{this.state['Amount'] && this.state['Amount'][i]}</td>
{this.state['Memo'] && this.state['Memo'].length > 0 ? <td>{this.state['Memo'][i]}</td> : undefined}
{this.state['Comment'] && this.state['Comment'].length > 0 ? <td>{this.state['Comment'][i]}</td> : undefined}
{this.state['Incurred Date'] && this.state['Incurred Date'].length > 0 ? <td>{this.state['Incurred Date'][i]}</td> : undefined}
{this.state['Entry Date'] && this.state['Entry Date'].length > 0 ? <td>{this.state['Entry Date'][i]}</td> : undefined}
<td>{this.state['Billable'] && this.state['Billable'][i]}</td>
<td>{this.state.fileName === 'expenses.csv' ? 'Expense' : 'Time'}</td>
</tr>
)
Somehow the conditions that are falsy still display empty <td>s to the table. What did I miss?
Empty columns are shown above.

You don't need to use ternary operators at all. Simply just chain &&'s.
{this.state.Memo && this.state.Memo[i] && <td>{this.state.Memo[i]}</td>}
{this.state.Comment && this.state.Comment[i] && <td>{this.state.Comment[i]}</td>}
{this.state['Incurred Date'] && this.state['Incurred Date'][i] && <td>{this.state['Incurred Date'][i]}</td>}
{this.state['Entry Date'] && this.state['Entry Date'][i] && <td>{this.state['Entry Date'][i]}</td>}
In addition, your array seems to be badly formatted:
// 2 separate variables for the same data?
this.memo = ['a', 'b'];
this.comment = ['comment', 'comment2'];
// Why not make it an array like this?
this.rows = [
{
Memo: 'a',
Comment: 'comment'
},
{
Memo: 'b',
Comment: 'comment2'
}
];
Then you can simply do:
this.rows.map(row => (
<tr key={row.id}>
<td>{row['Person Name']}</td>
<td>{row['Amount']}</td>
{this.state.hasMemos && <td>{row.Memo}</td>}
...
</tr>
)}
A <td> shouldn't be conditional on a row level, it should be conditional on a table level. You can't simply skip TD's if there isn't data for that row, as it will throw off the whole row by shifting columns over. You should either display N/A, an empty <td></td> for rows that may not have data, or hide them entirely on a table level via something like this.state.hasMemos, if there are any memos.
If you're using the new array structure I have listed, you can use this function to determine if any row has a memo:
this.array.some(row => row.Memo);
This will return true if any row has a Memo, thus either hiding the <td> for the entire table, or displaying it for every row.

Related

How to remove extra td on colspan reactjs

I have a table which contains td with colspan. Whenever I add colspan to a row, there is an additional or excess added cell.
const RenderTD = ({row}) => {
return table.cols.map(col => (
<td key={col}
colspan={row == 1? 2 : 1}>{col}</td>
))
}
here is the sample code: https://jsfiddle.net/e7gnd1tf/
How can I remove these?
Dont create components inside another component. Or use memoization
Td should be wrapped in tbody
Use key prop
The excess cell is being created because when you add colspan to a cell, it merges that cell with the next cell, effectively removing the next cell from the table. However, in your code, you are still creating that next cell, even though it has been merged with the previous cell. To fix this issue, you can conditionally render the extra cell only if colspan is not used, like this:
const RenderTD = ({ row }) => {
return table.cols.map((col, index) => (
<React.Fragment key={col}>
<td
colSpan={row === 1 && index === 0 ? 2 : undefined}
rowSpan={row === 2 && index === 0 ? 2 : undefined}
>
{col}
</td>
{index === 0 && row === 3 && <td></td>}
</React.Fragment>
));
};
In the above code, the extra cell is only rendered if colspan is not used and the row is the third row (row === 3) and the column is the first column (index === 0).

In React filter and map API in each client has different values ​and amounts of values and I need to make a generic code that works for any case

Explaining better, a client can have in the database that I receive the API, say, camera width, camera size and camera weight, and another client can have camera color, camera thickness, camera validity, camera depth, camera... So each client has different filters and different amount of filters. I need to make a generic code that works for any case, here is my code:
{APIData.filter(data => data.Result !== optionsData &&
(data.InspectionTime.replace(/-/g, "").slice(0, 8) >= startDate &&
data.InspectionTime.replace(/-/g, "").slice(0, 8) <= endDate))
.map((data, id) => {
const result = data.Result;
return (
<tr
key={id}
className="table-row"
onClick={() => {
setActiveRow(id);
setImageTest(id);
}}>
<td
className="table-data-date">
{data.InspectionTime}
</td>
<td
className="table-data-state">
{result == "-2" ? "Processado" : result == "-1" ? "Falha" :
result == "0" ? "Reprovado" : result == "1" ? "Aprovado" :
result == "2" ? "Ignorado" : null}
</td>
</tr>
)
})}
I already have in my code the result and date filters that are fixed for each client, I just need to implement these other filters that vary for each one, how can I do this within my code?

How to avoid display error when filtering > 0 in the middle of the map

I have a state "inventory", it stores item, price, qty, remark
when display using
this.state.inventory.filter(q => q.qty > 0).map
everything shows fine only if there's no 0 within
Googled and stackoverflowed, can't find any way to solve this..
new to ReactJS
{this.state.inventory.filter(q => q.orderQty > 0).map((val,ind) => (
<tr key={ind}>
<td>{ind}.{this.state.inventory[ind].item}</td>
<td>{(this.state.inventory[ind].price).toLocaleString()}</td>
<td>{(this.state.inventory[ind].orderQty).toLocaleString()}</td>
<td>{(this.state.inventory[ind].orderQty * this.state.inventory[ind].price).toLocaleString()}</td>
<td>{this.state.inventory[ind].orderRem}</td>
<td><button onClick={(e) => { this.handleDeleteRow(e,ind); }}>DEL</button></td>
</tr>
))
}
My question is, when looping inside the map, inventory[ind].orderQty === 0, the rest of the map becomes incorrect. how do I skip when inventory[ind].orderQty === 0?
its because you are using Index. using index is always a bad option. use the object directly instead "val"
{
this.state.inventory.filter(q => q.orderQty > 0).length>0?
this.state.inventory.filter(q => q.orderQty > 0).map((val,ind) => (
<tr key={ind}>
<td>{ind}.{val.item}</td>
<td>{(val.price).toLocaleString()}</td>
<td>{(val.orderQty).toLocaleString()}</td>
<td>{(val.orderQty * val.price).toLocaleString()}</td>
<td>{val.orderRem}</td>
<td><button onClick={(e) => { this.handleDeleteRow(e,ind); }}>DEL</button></td>
</tr>
)):""
}

AngularJS Custom Filter duplicates

I currently meet an issue with a custom filter. I have an array of CVE objects called 'cves' (in my scope), and for each item I generate a tr line in a table using ng-repeat.
Here is the global structure of a CVE:
cve: {
id: integer,
cve: string,
summary: text,
description: text,
cvss:{
score: float,
vector: string
}
}
Here is my HTML code
<input type='text' ng-model='searchField'/>
....
<tr ng-repeat="cve in cves | cveFilter:[ad_filters, searchField] as filtered_cves"
ng-if="cves.length > 0">
<td colspan="7" class="no-padding">
//printing infos in a custom directive
</td>
</tr>
....
Here is my filter :
.filter('cveFilter', function () {
return function (cves, params) {
console.log(cves);
let items = {
ids: params[0],//the ids (array of ids)
text: params[1],//the text
filtered_cves: []//the output
};
// for each cve, if its ID is in items.ids
// AND if one of the property of the CVE match with items.text
// push it to items.filtered_cves
cves.forEach(function (cve) {
if (
items.ids.includes(cve.id) &&
(
cve.cve.match(items.text) ||
cve.summary.match(items.text) ||
cve.description.match(items.text) ||
cve.cvss.score.match(items.text) ||
cve.cvss.vector.match(items.text)
)
) {
items.filtered_cves.push(cve)
}
});
return items.filtered_cves;
};
});
My problem is the following : my filter seems to work, it keeps only the matching CVEs but it displays each CVE in duplicate. That means if I have 6 cves in my $scopes.cves array, i will have 12 lines in my html table.
It's my first custom filter but I think it's a stupid mistake.
Do you know where I failed ?
Thanking you in advance,
It is duplicating the data, I don't get blank lines.
If I print the content of $scope.filtered_cves, I get (let's say I am supposed to get 8) 16 elements.
I didn't mention that earlier, but $scope.ad_filters is an array of CVE IDs I want to display. A CVE is displayed only if its ID is in $scope.ad_filters AND if one of its property matches with the content of the input form text.
I can't screenshot at the moment, I need to put fake data.
Here is the updated code for my filter (nothing really changed, just added some functions) :
.filter('cveFilter', function () {
return function (cves, params) {
let items = {
ids: params[0],
text: params[1],
filtered_cves: []
};
cves.forEach(function (cve) {
console.log(cve);
if (items.ids.includes(cve.id)) {
if (items.text === '' || items.text === null || items.text === undefined ||
(
cve.cve.toString().includes(items.text) ||
cve.summary.toString().includes(items.text) ||
cve.description.toString().includes(items.text) ||
cve.cvss.score.toString().includes(items.text) ||
cve.cvss.vector.toString().includes(items.text)
)
) {
items.filtered_cves.push(cve)
}
}
});
return items.filtered_cves;
};
});
I ran it and I noticed that it is executed several times, and the last time it prints twice too much lines.

React className with ternary operator add class 'null'

I'm trying to conditionally apply a class to my component using an expression like this:
.map(function(list, index) {
<div className={"myClass " + (position === index ? 'active' : null)}>
}
But it keeps adding null as class, with an end result like this:
<div class="myClass active">...
<div class="myClass null">...
This is a simple example, with only 2 class names, so I could just replace null with the default class name. But in a more complex layout, I would need to duplicate the same name over and over again.
Is there a better approach to solve this problem?
You could use an empty string '' instead of null like:
.map(function(list, index) {
<div className={"myClass " + (position === index ? 'active' : '')}>
}
Also map should return a value:
.map(function(list, index) {
return <div className={"myClass " + (position === index ? 'active' : '')}>;
}
If you have multiple classes, you might consider building the list of classes from an array:
var classes = ["myClass"];
if (position === index) {
classes.push('active');
}
return (
<div className={classes.join(' ')}>
...
</div>
);
You can also consider using a helper function that will generate the className string from an object like this:
var classes = {
myClass: true,
active: position === index
};
classnames is one such utility (not the only one).
Remove the space from "myClass " to "myClass", then replace null with an empty string ""
.map(function(list, index) {
<div className={"myClass" + (position === index ? 'active' : "")}>
}
just use https://www.npmjs.com/package/classnames:
usage example:
<div className={cn({"active": position === index })} ></div>
Use && short-circuiting: className={"myClass " + (position === index && 'active')}
In this way, if position === index is false, because we are using &&, we short-circuit. JS skips over 'active' and we just move on with our lives.
React Solution:
className={`myClass ${index ? "active" : ""}`}
Different syntax
className={`myClass ${index && "active"}`}

Resources