How can I exclude some keys from an array - javascript

I am working on the render method of a datatable component where I have to filter some headers.
I have this:
<TableRow>
{headers.map(header =>
checkboxes.map(checkbox =>
// HERE I NEED TO FILTER IT
checkbox.value === header.key && checkbox.checked && (
<TableHeader {...getHeaderProps({ header })}>
{header.header}
</TableHeader>
),
),
)}
</TableRow>
The checkbox.checked you see above returns true or false and I need 2 keys to ignore from those conditionals. It doesn't matter if the conditionals are met.
Here I am checking that the values of checkbox and header are the same checkbox.value === header.key but I need to exclude 2 header.key which are header.key === device and header.key === ticketNumber.
So I need to do something like:
<TableRow>
{headers.map(header =>
checkboxes.map(checkbox =>
// HERE I NEED TO FILTER IT
checkbox.value === header.key && checkbox.checked && (ignore header.key === 'device' && header.key === 'ticketNumber' from the conditionals above) (
<TableHeader {...getHeaderProps({ header })}>
{header.header}
</TableHeader>
),
),
)}
</TableRow>
I mean, those 2 headers I mentioned in the code above should never be hidden from the UI. When this condition checkbox.value === header.key && checkbox.checked is met, it will hide all of the headers with checkbox.checked === true, but I need a proper condition/function to ignore the header keys header.key === device and header.key === ticketNumber. Those 2 headers should always exist in the UI.
I am using lodash, I don't know if I can use it there or if there are any other options?

If I correctly understand your requirements, I think this should work:
<TableRow>
{headers.map(header =>
(['device', 'ticketNumber'].includes(header.key) || checkboxes.find(checkbox => header.key === checkbox.value).checked) &&
<TableHeader {...getHeaderProps({ header })}>
{header.header}
</TableHeader>
)}
</TableRow>

You can create a simple shouldShow() method outside of the component. The method should include your conditions, and return a boolean.
Since you've got 3 checkboxes, and you only want to show the relevant column, when one of them is checked, you should remove the 2nd map, and use Array.some() to check if at least one of the is checked, and it's value is the same value of the key.
Note: You don't need to actually filter the array, because react ignores false values in render (see conditional rendering). It also ignores undefined, and null.
const shouldShow = ({ key }, checkboxes) =>
key === 'device' ||
key === 'ticketNumber' ||
checkboxes.some(({ checked, value }) => checked && value === key);
<TableRow>
{headers.map(header =>
shouldShow(header, checkboxes) && (
<TableHeader {...getHeaderProps({ header })}>
{header.header}
</TableHeader>
),
)}
</TableRow>
A better solution would to create a Set of checked keys using Array.reduce(), and then check if the key exists in it while rendering the columns:
// in the render method
const shouldRenderSet = checkboxes.reduce((r, { checked, value }) =>
checked ? r.add(value) : r
, new Set(['device', 'ticketNumber']))
<TableRow>
{headers.map(header =>
shouldRenderSet.has(header.key) && (
<TableHeader {...getHeaderProps({ header })}>
{header.header}
</TableHeader>
),
)}
</TableRow>

Related

How to break a loop inside inline if in next js

I would like to stop the loop inside bedsAssign.map if row.id is not equal to u.tenant_id but putting break; doesn't work.
{tenants.map((row) =>
bedsAssign.map(
(u) =>
row.id !== u.tenant_id && (
<MenuItem key={row.id} value={row.id}>
{row.fullName}
</MenuItem>
break; --> not working
)
)
)}
You can add filter before map to remove all bedsAssign items which are not matched with current row.id
{
tenants.map((row) =>
bedsAssign
.filter((u) => row.id !== u.tenant_id)
.map((u) => (
<MenuItem key={row.id} value={row.id}>
{row.fullName}
</MenuItem>
))
)
}
If you want to break the loop, you can try to use some or find with a proper return for map
{
tenants.map((row) => {
const isAssigned = bedsAssign.some((u) => row.id !== u.tenant_id)
return isAssigned ? (<MenuItem key={row.id} value={row.id}>
{row.fullName}
</MenuItem>) : null
})
}
You can not break any array methods like forEach, filter, map etc. If you encounter a scenario where you want your loop to break then you should use traditional for loop.
use a filter instead of map for bedsAssign:
{tenants.map((row) =>
bedsAssign.filter(
(u) =>
row.id !== u.tenant_id && (
<MenuItem key={row.id} value={row.id}>
{row.fullName}
</MenuItem>
)
)
)}
the filter is going to only fill in the items that are meeting the condition you want.
EDIT: I noticed that you want to break once condition is met, this would work in this case:
{tenants.map((row) =>
for(let i of bedsAssign){
if(row.id !== i.tenant_id && (
<MenuItem key={row.id} value={row.id}>
{row.fullName}
</MenuItem>
)){
break
}
}
)
)}

React & clsx: add a class name if the current item in a mapped array is the first of multiple items

I've got the following code, and I need to add some new functionality to it. Basically, the map function is iterating through an array, and if there is only one item in the array, then I don't want to add a new class, but if this is the first item in an array of 2 items, I want to add a class name.
{section?.values?.link.map((link, index) => {
return (
<LinkComponent
key={index}
className={clsx({
"jc-left":
link?.values?.linkType !==
"primary-button",
})}
</LinkComponent>
...
I know it looks like the one that's already there, in that I put in the class name in quotes followed by a semicolon and then the rule, I just don't know how to write what seems like a complex rule to me. Any help is appreciated.
If I correctly understand your question is that you want to add className if you array.length > 1 and then add class to the first item of the array.
{section?.values?.link.map((link, index, self) => {
return (
<LinkComponent
key={index}
className={clsx({
"jc-left": link?.values?.linkType !== "primary-button",
"YOUR_CLASS": self.length > 1 && index === 0,
})}
</LinkComponent>
But what if you have more than two items then I assume that you will add class to all items except the last one
{section?.values?.link.map((link, index, self) => {
return (
<LinkComponent
key={index}
className={clsx({
"jc-left": link?.values?.linkType !== "primary-button",
"YOUR_CLASS": self.length > 1 && (index + 1 !== self.length),
})}
</LinkComponent>
If you want to render conditionally with clsx you can do this based on the two conditions:
{section?.values?.link.map((link, index) => {
// Checks if array has more than 2 or 2 items and if the index is 0 which means that is the first item.
const hasConditionalClass = section?.values?.link.length >= 2 && index === 0;
return (
<LinkComponent
key={index}
className={clsx({
"jc-left": link?.values?.linkType !== "primary-button",
"condtional-class": hasConditionalClass
})}
</LinkComponent>
...

Return a message if nothing is found after filtering an array in JavaScript

I'm getting an array in React/Next, but before mapping through it I'm running a few filters and sorting it. I'd like to display a simple message if nothing gets found after the filters run. It should display a span with a simple text string (jsx).
The code is pretty simple:
{ArrayObj
.filter(
(item) =>
// conditions here
)
.sort((a, b) => (a.condition > b.condition ? 1 : -1))
.map((item) => (
<li key={item._id}>
// contents of each iteration here
</li>
))}
I'm guessing it should go just right before the .map but I'm stuck at this.
The full code is as follows:
{yachtListings
.filter(
(yacht) =>
yacht.buildYear >= searchYear &&
yacht.price <= searchPrice &&
yacht.length <= searchLength &&
yacht.type === searchType
)
.filter((yacht) =>
filterKeyword == ''
? yacht
: yacht.modelName
.toLowerCase()
.includes(filterKeyword.toLowerCase())
)
.sort((a, b) => (a[sortOrder] > b[sortOrder] ? 1 : -1))
.map((yacht) => (
<li key={yacht._id}>
<SecondHandCard /> // card components with props
</li>
))}
You can see a live demo here. I'm a front-end guy taking my skills to the back-end more and more.
You could filter the array first and then conditionally show jsx depending on the length of that.
let filteredArray = ArrayObj.filter((item) => {...}).sort(...);
{filteredArray.length == 0 ? <span>Message</span> : filteredArray.map(...)}

How to conditionally set an onClick attribute to component without having to repeat lines of nested code?

I'm working inside a functional React component and I'm trying to render a 'Card' component with a conditional onClick based on the member's typename. A card should be clickable if its typename is 'Bundle' or 'LegacyArticle'. Any other typename should not have an onClick property.
I'm having trouble finding an efficient way to apply an onClick to a card without having a conditional with a bunch of lines of repeated code, (the code that would essentially be nested children of the 'Card' component).
So far, I've been able to render it conditionally from a function. This allows my return statement to have better readability but there is still a big chunk of code that gets repeated, and I want to find a way to reduce that.
return (
<div css={cards}>
{members && members.map((member, index) => (
renderCard(member, index)
))}
</div>
);
const renderCard = (member, index) => {
const isClickable = member.__typename === 'Bundle' || member.__typename === 'LegacyArticle';
if (isClickable) {
return <Card key={index} css={card} onClick={() => onCardClick(member)}>
{(member.__typename !== 'LessonSpark' &&
schemas[member.__typename].image(member)) &&
(<CardImage src={schemas[member.__typename].image(member)} />)}
<CardBlock css={cardType}>
{member.label || schemas[member.__typename].typename}
</CardBlock>
<CardBlock css={cardTitleStyle}>
{_truncate(schemas[member.__typename].title(member), 60)}
</CardBlock>
</Card>
} else {
return <Card key={index} css={card}>
{(member.__typename !== 'LessonSpark' &&
schemas[member.__typename].image(member)) &&
(<CardImage src={schemas[member.__typename].image(member)} />)}
<CardBlock css={cardType}>
{member.label || schemas[member.__typename].typename}
</CardBlock>
<CardBlock css={cardTitleStyle}>
{_truncate(schemas[member.__typename].title(member), 60)}
</CardBlock>
</Card>
}
};
As you can see, <Card> has children nested, and it's a lot of lines of code being repeated when essentially, the only difference is one has an onClick and one doesn't.
Any ideas on how I could possibly reduce this code and find a clean way to apply an onClick conditionally?
You can basically use DRY principle by only changing the unique / custom parts. So in this case the only difference i could see between the if and else statements was the onClick, so you can instead put the conditional there, if you want an onClick, in this case use a ternary or you could use &&.
const renderCard = (member, index) => {
const typename = member.__typename;
const isClickable = typename === 'Bundle' || typename === 'LegacyArticle';
const schema = schemas[typename];
const image = schema.image(member);
const cardContents = (typename !== 'LessonSpark' &&
image) &&
(<CardImage src={image} />);
return (<Card key={index} css={card} onClick={ isClickable ? () => onCardClick(member) : undefined}>
{cardContents}
<CardBlock css={cardType}>
{member.label || schema.typename}
</CardBlock>
<CardBlock css={cardTitleStyle}>
{_truncate(schema.title(member), 60)}
</CardBlock>
</Card>);
}
In addition to that, you can save the values from function calls and from properties of objects so that a) the code is easier to read/follow, and b) the code is a bit more optimized, since it is not doing unnecessary extra function calls.
One way to accomplish this is by adding the condition inside your Card onClick prop:
const isClickable = member.__typename === 'Bundle' || member.__typename === 'LegacyArticle';
return <Card key={index} css={card} onClick={isClickable ? () => onCardClick(member): null}>
{(member.__typename !== 'LessonSpark' &&
schemas[member.__typename].image(member)) &&
(<CardImage src={schemas[member.__typename].image(member)} />)}
<CardBlock css={cardType}>
{member.label || schemas[member.__typename].typename}
</CardBlock>
<CardBlock css={cardTitleStyle}>
{_truncate(schemas[member.__typename].title(member), 60)}
</CardBlock>
</Card>
Put onClick into an object and use prop spread.
const renderCard = (member, index) => {
const typename = member.__typename;
const maybeOnClick = typename === 'Bundle' || typename === 'LegacyArticle' ?
{ onClick: () => onCardClick(member) } : {};
const schema = schemas[typename];
const image = schema.image(member);
const cardContents = (typename !== 'LessonSpark' &&
image) &&
(<CardImage src={image} />);
return (<Card key={index} css={card} {...maybeOnClick}>
{cardContents}
<CardBlock css={cardType}>
{member.label || schema.typename}
</CardBlock>
<CardBlock css={cardTitleStyle}>
{_truncate(schema.title(member), 60)}
</CardBlock>
</Card>);
}

How to conditionally enable onClick

I'm creating a table by mapping my data , I'm trying to create a TableSortLable , but I only want to enable the users to sort by the first two columns .
{
Columns.map(c,index => {
return (
<TableCell key={c.key} component="th" padding="checkbox">
<TableSortLabel
active={props.brokersListOrderByColumn === c.key}
direction={props.brokersListSortOrder}
onClick={() => props.setOrderBy(c.key)}>
{c.label}
</TableSortLabel>
</TableCell>
)
})
}
I was thinking on using the onClick event, and to enable it only when its the first or second column . I tried something like:
{(index === 1 || index ===2) && onClick={() => props.setOrderBy(c.key)}}>
Or conditionally setting the onClick value , but both didn't work .
I've also tried conditionally setting the TableSortOrder :
<TableCell key={c.key} component="th" padding="checkbox">
{(index === 1 || index === 2) && <TableSortLabel
active={props.brokersListOrderByColumn === c.key}
direction={props.brokersListSortOrder}
onClick={() => props.setOrderBy(c.key)}>
{c.label}
</TableSortLabel>}
{(index !== 1 && index !== 2) && c.label}
</TableCell>
But it complains c is undefined
Syntax error. You should write parameters in brackets for functors. Like this
Columns.map((c, index) => {...
Otherwise engine will try to find (RHS) variable c (expected as function).
You didnt init such variable and get this
But it complains c is undefined .
Try onClick={(index === 1 || index === 2) ? () => props.setOrderBy(c.key) : null}
Try to give something like
onClick={
() => (index === 1 || index === 2) && props.setOrderBy(c.key)
}
Maybe you can just add check inside handler?
onClick={() => {
if((index === 1 || index ===2)) {
props.setOrderBy(c.key)}
}
}}
You can also extract array function for breivity:
const onClick = () => {
if((index === 1 || index ===2)) {
props.setOrderBy(c.key)}
}
}
....
onClick={onClick}>

Categories