Use conditional logic in an array map? - javascript

Ok so I am using express-react-views as a templating engine and I am currently trying to make a working breadcrumb. On each route I pass a prop called "crumb" that is an array of the current location on the app. That array looks like this:
[
{
text: "Home",
href:"/",
active: false
},
{
text: "Step2",
href:`/page`,
active: true
}
]
Obviously this can be multiple steps down. The last step is the page you are on, so active is set to true. This is where my problem is. To render this on the page I am mapping this array to JSX like this:
const Breadcrumb = props => {
return (
<ol class="breadcrumb">
{props.crumb.map(crumb =>
<li class="breadcrumb-item"><a href={crumb.href}>{crumb.text}</a></li>
)}
</ol>
)
}
This code works fine but what the active page should have the class "active" on it and should not have an "a" tag. So what I need to do it as it's mapping this array to check for the active:true value and then map a different element. I hope that makes sense.

Hi you can try this out if you want both active and inactive links to be shown:
const Breadcrumb = props => {
return (
<ol class="breadcrumb">
{props.crumb.map(crumb =>
crumb.active ? <li class="breadcrumb-item"><a href={crumb.href}>{crumb.text}</a></li> : <li class="breadcrumb-item">{crumb.text}</li>
)}
</ol>
)
}
if you only want to show active links then you can use:
const Breadcrumb = props => {
return (
<ol class="breadcrumb">
{props.crumb.filter(item => item.active).map(crumb =>
<li class="breadcrumb-item"><a href={crumb.href}>{crumb.text}</a></li>
)}
</ol>
)
}
Inside map you can check crumb.active, so it will either return true or false and based on that you can return respective element.

Is this what you want
const Breadcrumb = props => {
return (
<ol class="breadcrumb">
{props.crumb.map(crumb => {
if(crumb.active)
return <li class="breadcrumb-item active"></li>
else
return <li class="breadcrumb-item"><a href={crumb.href}>{crumb.text}</a></li>
})}
</ol>
)
}

Related

React how to call function depends on specific element id

how I can tigress an function depends on id of an element. Right now all elements getting clicked if I click on any single element. how to prevent to show all element ? here is my code
const[showsubcat,setShowSubCat] = useState(false)
let subcategory=(()=>{
setShowSubCat(prev=>!prev)
})
my jsx
{data.map((data)=>{
return(
<>
<li class="list-group-item" id={data.id} onClick={subcategory} >{data.main_category}</li>
{showsubcat &&
<li><i class="las la-angle-right" id="sub_category"></i> {data.sub_category}</li>
}
</>
)
see the screenshot. I am clicking on single items but it's showing all items.
Every li should have it own state
so it's either you create states based on number of li if they're just 2 elements max! but it's ugly and when you want to add more li it's gonna be a mess
so you just create a component defining the ListItem and every component has it own state.
function ListItem({data}) {
const[showsubcat,setShowSubCat] = useState(false)
const subcategory= ()=> setShowSubCat(prev=>!prev)
return (
<>
<li class="list-group-item" id={data.id} onClick={subcategory} >
{data.main_category}
</li>
{showsubcat &&
<li>
<i class="las la-angle-right" id="sub_category"></i>
{data.sub_category}
</li>
}
</>
)
}
and you use it in the list component like this
data.map((datum, index) => <ListItem key={index} data={datum} />
EDIT AFTER THE POST UPDATE (misunderstanding)
the list item (or the block containing the li and the helper text) should be an independant component to manage it own state
function PostAds(data) => {
return (
<>
{
data.map((data, index) => <ListItem key={index} data {data}/>
}
</>
)
}
function ListItem({data}) {
const [showsubcat, setShowSubCat] = useState(false)
const subcategory = () => setShowSubCat(prev => !prev)
return (
<>
<li
class="list-group-item"
id={data.id}
onClick={subcategory}>
{data.main_category}
</li>
{
showsubcat &&
<li >
<i class = "las la-angle-right" id = "sub_category"></i>
{data.sub_category}
</li>
}
</>
)
}
The reason this is happening is because you are using the same variable showsubcat to check if the category was clicked or not.
A proper way to do this would be by either making showsubcat as an array that holds ids of those categories that were clicked like:
const[showsubcat,setShowSubCat] = useState([])
let subcategory=((categoryId)=>
showsubcat.includes(categoryId) ?
setShowSubCat(showsubcat.filter(el => el !== categoryId)) :
setShowSubCat([...showsubcat, categoryId]));
and then while mapping the data:
{data.map((category)=>
(
<>
<li class="list-group-item" id={category.id} key={category.id}
onClick={() => subcategory(category.id)}>
{category.main_category}
</li>
{showsubcat.includes(category.id) &&
<li>
<i class="las la-angle-right"
id="sub_category" key={`subCategory${category.id}`} />
{category.sub_category}
</li>
}
</>
)
}
The other method would be to add a new key in your data array as selectedCategory and change its value to true/false based on the click, but this is a bit lengthy, let me know if you still want to know that process.
Also, accept the answer if it helps!

How to dynamically map an array within another array in React?

I'm building a table of content using React. I'm calling my database to fetch each array(which are always different depending on query). I would like to render each child array when I click on the parent item. Here's conceptually what I want:
<ul id="parent" onClick={renderChildArray()}>
<li id="child" onClick={renderChild2Array()}>
{child2array}
<li>
</ul>
Here's my code:
tableOfContent = () => {
const { TOC, headers2 } = this.state;
return (
<div>
{TOC.map((header) => (
<ul
key={header.index}
onClick={() =>
this.handleHeaderClick(
header.level,
header.treepath,
header.containsLaw,
header.sections,
header.ObjectId,
)
}
className="TOC TOCsection"
>
{header._id}
{headers2.map((i, index) => (
<li
className="TOCsection"
style={{ listStyle: "none" }}
key={index}
>
{i._id}
</li>
))}
</ul>
))}
</div>
);
};
Right now, when I click on the parent the child appears on each parent key item. I want the child array to render under the parent that I clicked only. How to do that?
You can save the clicked parent's index in the state. And when rendering child items check if the current parentIndex === saveIndex and then render the child. I can write the pseudocode for this as I don't have a working version of your problem.
tableOfContent = () => {
const { TOC, headers2 } = this.state;
return (
<div>
{TOC.map((header, parentIndex) => (
<ul
key={header.index}
onClick={() =>
this.handleHeaderClick(
header.level,
header.treepath,
header.containsLaw,
header.sections,
header.ObjectId,
);
saveTheIndex(parentIndex); // This method should save parentIndex in the state. I am assuming the state variable is named 'clickedParentIndex'.
}
className="TOC TOCsection"
>
{header._id}
{ clickedParentIndex === parentIndex && headers2.map((i, index) => (
<li
className="TOCsection"
style={{ listStyle: "none" }}
key={index}
>
{i._id}
</li>
))}
</ul>
))}
</div>
);
};

Loop inside JSX

I have an object similar to this:
{
id: number,
kids: [{
id: number,
kids: [{
id: number,
kids: []
}]
}]
}
So it has property kids which is an array of kids each of which might have its own array of kids. I need to render original object in a tree view list like this:
<ul>
{object.map(item => (
<li>
<p>{item.value}</p>
{item.kids ?
{item.kids.map(item => (
<ul>
<li>
<p>{item.value}</p>
</li>
</ul>
))}
: null
}
</li>
))}
</ul>
So every item of kids will be a <li></li> element with <ul></ul> inside of it if item.kids isn't empty array.
I could keep going like this but it's pretty messy and more importantly I don't know when exactly kids property will be an empty array. So I need somehow loop over original object and it's properties and do something like
while (kid.kids) {
return {kid.kids.map(kid => (
<li>
<p>{kid.value}</p>
<ul>
kid.kids.map(kid => (
etc
))
</ul>
</li>
}))
}
But I can't understand the better way to loop like this.
This is probably best solved with recursion.
const Kids = ({id, kids}) => {
return {
<li key={id}>
<p>{id}</p>
{kids
? (<ul>{kids.map((kid) => <Kids id={kid.id} kids={kid.kids} />)}</ul>)
: null;
}
</li>
}
};

Apply Class if Child element exist in a loop for mega menu with React

I am trying to create a menu structure in Reactjs but having some difficulty in design. There are certain elements which I need to style differently.
So I need to apply different class to parent of those elements.
Menu Structure Below:
<div>
<ul>
<li className={this.state.dropdownClass ? "nav-item" : "no-child-dropdown nav-item"}>
<h5>Text</h5>
//first loop
<ul>
//this is in second loop
<li className="menuitem" >some text</li>
</ul>
</li>
<li className={this.state.dropdownClass ? "nav-item" : "no-child-dropdown nav-item"}>
<h5>Text</h5>
//first loop
<ul>
//this is in second loop
<li className="menuitem" >some text</li>
</ul>
</li>
<li className={this.state.dropdownClass ? "nav-item" : "no-child-dropdown nav-item"}>
<h5>Text</h5>
//first loop
<ul>
//No data in this but still it will apply the class to parent
</ul>
</li>
</ul>
</div>
Already tried with below:
var elementAll = document.getElementsByClassName("childrenmenu");
if (elementAll) {
this.setState({dropdownClass: true});
}
Sample code for above explanation:
<li className={this.state.dropdownClass ? "no-child-dropdown nav-item ddfull subnav" : "nav-item ddfull subnav"}><a className="nav-link" href={this.props.data.length ? "/" + this.props.params[0] + "-" + this.props.params[1] + (this.props.data && this.props.data[9] && this.props.data[9].link ? this.props.data[9].link : "") : "#"}>{rtl === "en" ? this.state.englishMenu[9] : this.state.arabicMenu[9]}</a>
<div className="ddmenu">
<div className="navContent row headRow d-flex justify-content-start">
{this.props && this.props.data && this.props.data.length ? this.props && this.props.data && this.props.data[9] && this.props.data[3].children && this.props.data[9].children.map((data, idx) => {
return <div className="col" key={idx}>
<h5><a href={"/" + this.props.params[0] + "-" + this.props.params[1] + data.link}>{data.name}</a></h5>
<ul className="parentelement">
{data.children && data.children.length > 0 && data.children.map((data1, idx) => {
return <li key={idx} className="childrenmenu"><a href={"/" + this.props.params[0] + "-" + this.props.params[1] + data1.link}>{data1.name}</a></li>
})}
</ul>
</div>
}) : ""}
</div>
</div>
</li>
Note: The blank UL should be ignored and the class (no-child-dropdown nav-item) should not be applied to the parent if it does not have any child element.
Problem: It is applying class to all the parent elements.
I think you need to modify the approach you take to generate this menu. You should be deciding what classes to use during the initial creation of the menu, not retroactively inject classes via DOM manipulation.
It looks like you're using data from props. It would make sense to consolidate that data into a structure that will be easily manageable
menus: [
{ category: "fruit", links: ["apple", "orange", "pineapple"] },
{ category: "animals", links: ["cat ", "dog", "rabbit"] },
{ category: "empty", links: [] }
]
Here's a working sandbox: https://codesandbox.io/s/xenodochial-wilbur-r4xvy
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
state = {
menus: [
{ category: "fruit", links: ["apple", "orange", "pineapple"] },
{ category: "animals", links: ["cat ", "dog", "rabbit"] },
{ category: "empty", links: [] }
]
};
createMenus = () => {
const { menus } = this.state;
const sections = menus.map(item => {
console.log(item.links.length);
return (
<li
className={
item.links.length > 0 ? "nav-item" : "no-child-dropdown nav-item"
}
>
<h5>{item.category}</h5>
<ul>
{item.links.map(link => {
return <li className="menuitem">{link}</li>;
})}
</ul>
</li>
);
});
return <ul>{sections}</ul>;
};
render() {
return <div>{this.createMenus()}</div>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
With that sort of data-structure, the styling can be determined in-line with the creation of the menu.
Just check the length of items on which you are looping over
<li className={loopedItems.length > 0 ? "nav-item" : "no-child-dropdown nav-item"}>
<ul>
//this is in loop
<li className="childrenmenu" >some text</li>
</ul>
</li>
Here you must be looping on an array or some other DS most probably.
Try to structure your data if not already structure in a way that loop of li items's are having a separate array. for eg:-
[{menu: [listItem1, listItem2 ...]}, {menu: [listItem1, listItem2 ...]}]
then loop through object to create ul and inside that loop create a list for every array of menu1, menu2 ... key.
for className you can use classnames npm package which will make your code more readable and easier to maintain.
Here is a sample:
let megaMenu = [];
Object.keys(obj).forEach(function(key) {
const [menu] = obj[key];
const ULClasses = classnames('default-classnames', {
'nav-items': menu.length,
'no-child-dropdown nav-item': !menu.length
})
const menu = (
<ul className={ULClasses}>
{
menu.length && menu.map(listItem => {
<li>{listItem}</li>
})
}
</ul>
)
megaMenu.push(menu)
}
return megaMenu;
You can extract the looping logic to a method.
After that you can check the results of said method inside your li item.
Something like this
<li className={this.shouldLoop() > 0 ? "" : ""}>
And the method
shouldLoop(){
return (this.props && this.props.data &&
this.props.data.length ? this.props && this.props.data && this.props.data[9] &&
this.props.data[3].children && this.props.data[9].children)
}
Please note that as it is the code is very verbose and hard to understand.
I would suggest you try and split to smaller methods, this will help you.

Enzyme `.find(selector)` does not seem to find a selector

element.find('span.active-nav-option') returns nothing whilst element.find('.active-nav-option') returns the span. The point of the test is to find out if a span was rendered instead of a Link.
Component is as follows:
const PageNav = ({
router,
slides,
}) => (
<nav className="PageNav">
<span className="chevron">
<MoreVerticalIcon
strokeWidth="0.5px"
size="3em"
/>
</span>
<ul className="nav-links">
{mapSlides(slides, router)}
</ul>
<style jsx>{styles}</style>
</nav>
)
function mapSlides(slides, router) {
return Object.entries(slides)
.sort((
[, { order: a }],
[, { order: b }],
) => a - b)
.map(([slidename, { altText, order }]) => {
const isActiveLink = router.query.slidename === slidename
const navItemClassnames = [
'nav-item',
isActiveLink && 'active',
]
.filter(Boolean)
.join(' ')
const Element = isActiveLink
? props => <span {...props} />
: Link
const liInternalElementProps = {
...(isActiveLink && { className: 'active-nav-option' }),
...(!isActiveLink && {
href: `/CVSlide?slidename=${slidename}`,
as: `/cv/${slidename}`,
}),
}
return (
<li
className={navItemClassnames}
key={order}
>
<Element {...liInternalElementProps}>
<a title={altText}>
<img
src={`/static/img/nav-icons/${slidename}.svg`}
alt={`An icon for the ${slidename} page, ${altText}`}
/>
</a>
</Element>
<style jsx>{styles}</style>
</li>
)
})
}
To Reproduce
run this line as a test:
const wrapperOne = shallow(
<PageNav
slides={mockSlides}
router={{
query: {
slidename: 'hmmm',
},
}}
/>
)
const spanExists = wrapperOne
.find('.active-nav-option')
.html() // outputs <span class="active-nav-option">...</span>
// so one would expect span.active-nav-option to work?
const spanDoesNotExist = wrapperOne
.find('span.active-nav-option')
.html() // throws an error `Method “html” is only meant to be run on a single node. 0 found instead.`
// subsequently if I use `.exists()` to test if the element exists, it returns nothing.
Expected behavior
element.find('span.active-nav-option') should return the span. I think? I initially thought this was to do with shallow vs mount but the same happens with mount. Am I being an idiot here? Is this something to do with the map function in the component?
OS: OSX
Jest 23.5
enzyme 3.5.0
Looks like that's because you don't use <span/> directly in JSX returned from render method but assign it to a variable and then add that variable to JSX.
If you execute console.log(wrapperOne.debug()) you will see the following result (I've removed styles and components that you didn't provided):
<nav className="PageNav">
<span className="chevron" />
<ul className="nav-links">
<li className="nav-item active">
<Component className="active-nav-option">
<a title={[undefined]}>
<img src="/static/img/nav-icons/0.svg" alt="An icon for the 0 page, undefined" />
</a>
</Component>
</li>
</ul>
</nav>
As you can see, you have <Component className="active-nav-option"> instead of <span className="active-nav-option"> thus span.active-nav-option can't find anything.

Categories

Resources