Target specific child inside React loop with refs and state onClick - reactjs

I've got a mobile nav menu with a couple dropdown menus:
/* nav structure */
/about
/portfolio
/services
/service-1
/service-2
contact
etc..
I'm creating this menu dynamically with a loop in my render function.
I've set up a state variable to assign a class to the active dropdown menu. I'm using an onClick event/attribute on the trigger element (a small arrow image) to apply an active class to the respective dropdown menu. Kinda...
const myNavMenu = () => {
const [isSubMenuOpen, toggleSubMenu] = useState(false)
return (
<nav id="mainNav">
<ul>
{navItems.items.map((item, i) => (
<li key={i} className={
(item.child_items) !== null ? 'nav-item has-child-items' : 'nav-item'}>
<Link to={item.slug}>{item.title}</Link>
{(item.child_items === null) ? null :
<>
<img className="arrow down" src={arrowDownImg} onClick={() => toggleSubMenu(!isSubMenuOpen)} />
{printSubMenus(item)}
</>
}
</li>
))}
</ul>
</nav>
)
}
/**
* Prints nav item children two levels deep
* #param {Obj} item a navigation item that has sub items
*/
function printSubMenus(item) {
return (
<ul className={(isSubMenuOpen.current) ? "sub-menu sub-menu-open" : "sub-menu"}>
{item.child_items.map( (childItem, i) => (
<li className="nav-item" key={i}>
<Link to={childItem.slug}>{childItem.title}</Link>
{(childItem.child_items === null) ? null :
<>
<ul className="sub-sub-menu">
{childItem.child_items.map( (subItem, i) => (
<li className="sub-nav-item" key={i}>
<img className="arrow right" src={arrowRight} alt={''} />
<Link to={subItem.slug}>{subItem.title}</Link>
</li>
))}
</ul>
</>
}
</li>
))}
</ul>
)
}
}
*<Link> is a Gatsby helper component that replaces <a> tags.
The issue is that when I click my trigger, the active class is being applied to both (all) sub-menus.
I need to insert some sort of index (or Ref) on each trigger and connect it to their respective dropdowns but I'm not quite sure how to implement this.
I was reading up on useRef() for use inside of function components. I believe that's the tool I need but I'm not sure how to implement it in a loop scenario. I haven't been able to find a good example online yet.
Thanks,
p.s. my functions and loops are pretty convoluted. Very open to refactoring suggestions!

Move sub-menu to a new react component and put all the related logic in there (applying class included).

Related

Is It possible to change the property from hover to click during a specific break point using media queries in React?

Right now my li tag has a mouseenter and mouseleave effect, but when I shrink my site down to mobile view, I want it to change to onClick instead.
So the code below shows my menu with the dropdown. Whenever I click on the hamburger menu in mobile view, it will trigger the active class on the <ul> and open up the mobile view. Then when I hover over the li tag it will display the dropdown menu.
Instead of it displaying when I hover, I need it to display when I click the ul tag only at the breakpoint of 960px.
<ul className={click ? 'nav-menu active' : 'nav-menu'}>
<li
className='nav-item'
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
<Link to='/' className='nav-links' onClick={() => setClick(false)}>
Home <i class='fas fa-caret-down' />
</Link>
{dropdown && <Dropdown />}
</li>
<li className='nav-item'>
<Link
to='/services'
className='nav-links'
onClick={() => setClick(false)}
>
Services
</Link>
</li>
</ul>
Is that something that is posssible to do? Or would I have to completely redo my code?
It most definitely is. And there are many options of how you could do it, these are some that come to my mind at first:
Option #1:
You could render 2 completely different things based on media query, something like this:
<div className="hidden-xs">
<ol onClick={() => {}}></ol>
</div>
<div className="hidden-lg">
<ol onMouseLeave={() => {}} onMouseEnter={() => {}}></ol>
</div>
Option #2:
You could disable certain functions based on current viewport
const onMouseEnter = () => {
if (window.width > something) {
return;
}
do something otherwise
}
const onClick = () => {
if (window.width < something) {
return;
}
do something otherwise
}
I would recommend taking a look at some of these articles & modules:
Medium post about media queries in React
react-media
react-responsive

Add css class onClick to idividual elements generated by mapping an array in React

<div className="sub-nav">
<ul>
{props.data.map((item, index) => {
return (
<li key={index} className="sub-nav-item" >{item.name} </li>
);
})}
</ul>
</div>
Hi! I want to add an active class to an individual li when clicked, but I cannot seem to get it to work on mapped elements without adding it to all of them. This is part of a menu component in a React project. Thanks!
The following is hooks implementation.Incase you're using class I think you can derive from the same.
const [selectedItemIndex,setSelectedItemIndex] = useState(null);
<div className="sub-nav">
<ul>
{props.data.map((item, index) => {
return (
<li key={index} onClick={()=>setSelectedItemIndex(index)} className={`sub-nav-item ${selectedItemIndex===index?'active':''}`} >{item.name} </li>
);
})}
</ul>
</div>

React State Hook - toggle a class

I'm trying to build a sidebar navigation menu and thought I'd take advantage of the new State hook in React. I've read the docs but can't seem to find an example similar to what I need, which is quite simply to toggle a CSS class on click which will in turn open and close my menu.
Here's what I've tried:
const SidebarMenuItem = ({ component }) => {
const [ menuActive, setMenuState ] = useState(false);
return (
<li className="p-sidebar-menu-item">
menuActive:
{ menuActive }
<button className="p-sidebar-menu-item__link" onClick={() => setMenuState(!menuActive)}>{ component.component }</button>
{ component.children && (
<ul className="p-sidebar-menu">
<li><a href={`/${component.slug}`}>Overview</a></li>
{ component.children.map((subPage, key) => (
<li key={ key }>
<a href={`/${subPage.slug}`}>{ subPage.name }</a>
</li>
))}
</ul>
)}
</li>
)
}
export default SidebarMenuItem;
Any ideas where I'm going wrong?
Thanks
Just make the className dynamic, so instead of setting
<li className="p-sidebar-menu-item">
transform it in a template literal
<li className={`p-sidebar-menu-item`}>
and then add your class conditionally (the "yellow" class in my example)
<li className={`p-sidebar-menu-item ${menuActive ? "yellow" : ""}`}>
Take a look at this CodeSandbox: here I've just added your component and changed the way the className attribute is generated.
If you want to avoid the ternary operator you could use the classnames module and then update your code to
import c from "classnames";
...
...
...
<li className={c("p-sidebar-menu-item", {yellow: menuActive})}>
Another clean solution can be to generate the className string in advance, for example
let classes = "p-sidebar-menu-item";
if(menuActive) {
classes += " yellow";
}
<li className={classes}>
Let me know if you need some more help 😉
I think you just need
const [ menuActive, setMenuState ] = useState(false);
change the name of setState to setMenuState in your code also
Don't forget to use the prevState or you can have a bug.
<button
className="p-sidebar-menu-item__link"
onClick={() => setMenuState((prevMenuActive) => !prevMenuActive)}>
{component.component}
</button>

In a React Component, how to conditionally show a style while iterating over a list

I have a React component that list out all users and their point rankings. I want to specific which row it the currentUser. See component:
const RankingsList = ({rankings, currentUserId}) => {
return (
<ul className="list-group">
{rankings.map(r =>
<li className="list-group-item" key={r.user_id}>
<p key={ranking.user_id}>{r.display_name} - {r.points}</p>
<p>!{currentUserId}!</p>
</li>
)}
</ul>
);
};
For each iteration of rankings, I have the r.user_id and the currentUserId. What I would like to do is when the r.user_id == currentUserId apply a class like active.
Should I be doing this inline or should this be done in the ranking array on the API or in some area of React like to reducer?
You can do it inline, for example:
<li
className={`list-group-item ${r.user_id == currentUserId ? 'active' : ''}`}
key={r.user_id}
>
If you think it's too verbose, you can also extract it in a function
<li className={getClassNames(r.user_id)} key={r.user_id}>
What's good with React is that's just javascript, so you can do it the way you would without JSX.
Just replace your current map by:
{rankings.map(r => {
const active = r.user_id === currentUserId ? 'active' : '';
return (
<li className={`list-group-item ${active}`} key={r.user_id}>
<p key={ranking.user_id}>{r.display_name} - {r.points}</p>
<p>!{currentUserId}!</p>
</li>
})
)}

Show loading spinner while waiting for data to be fetched in ReactJS render

I'm trying to write ReactJS component that will be showing 2 view types toggled by loaded flag in the state. Im asking about a loader spinner but consider that this issue can be more complicated for showing 2 different DOM trees based on that flag. I would really like to make my code as clearer as I can.
What I have in mind is 2 ways to write it:
First way is very intuitive for me but not readable + I have to add another wrapping div inside the else statement:
render() {
return (
<div>
{!this.state.loaded ? <Loader /> : (
<div>
<Arrow direction="left" onClick={this.slideLeft}/>
<ul>
{
this.props.images.map((image, index) => {
return (
<li key={index} styleName={index === this.state.active ? 'active' : ''}>
<ImageItem src={image} />
</li>
)
})
}
</ul>
<Arrow direction="right" onClick={this.slideRight}/>
</div>
)}
</div>
);
}
The second way is to return before rendering any of the loaded code, is it a good practice to keep logic inside the render function like this? + "content" class div is duplicated:
render() {
if(!this.state.loaded){
return <div className="content"><Loader /></div>;
}
return (
<div className="content">
<Arrow direction="left" onClick={this.slideLeft}/>
<ul>
{
this.props.images.map((image, index) => {
return (
<li key={index} styleName={index === this.state.active ? 'active' : ''}>
<ImageItem src={image} />
</li>
)
})
}
</ul>
<Arrow direction="right" onClick={this.slideRight}/>
</div>
);
}
maybe you have other suggestions for me? thanks.
As far as I know it is a common practise. However, you should refactor your components in a way, that there is only one or none piece of logic in your render function.
Your parent component observes the loading state and renders the loader component or the "loaded" component.
If your state logic gets more advanced you then should take a look at the Flux architecture and its implementation library Redux.

Resources